-- Operations channel data model (no service-days table):
-- schedules -> trips -> trip_stops -> stop_updates

create table if not exists public.operations_schedules (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  channel_id text not null references public.channels(id) on delete cascade,
  version integer not null check (version > 0),
  source_file_name text not null,
  source_mime text,
  storage_path text,
  file_sha256 text,
  parser text not null default 'unknown',
  parse_status text not null default 'pending',
  parse_error text,
  uploaded_by uuid not null references auth.users(id) on delete restrict,
  uploaded_at timestamptz not null default now(),
  parsed_at timestamptz,
  is_active boolean not null default false,
  unique (channel_id, version)
);

create unique index if not exists idx_operations_schedules_active_per_channel
  on public.operations_schedules(channel_id)
  where is_active = true;

create index if not exists idx_operations_schedules_channel_uploaded
  on public.operations_schedules(channel_id, uploaded_at desc);

create table if not exists public.operations_trips (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  schedule_id text not null references public.operations_schedules(id) on delete cascade,
  trip_number text not null,
  duty_number text,
  running_number text,
  direction text,
  service_code text,
  sort_order integer not null default 0,
  created_at timestamptz not null default now()
);

create index if not exists idx_operations_trips_schedule_sort
  on public.operations_trips(schedule_id, sort_order, trip_number);

create table if not exists public.operations_trip_stops (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  trip_id text not null references public.operations_trips(id) on delete cascade,
  stop_sequence integer not null check (stop_sequence > 0),
  stop_name text not null,
  stop_code text,
  scheduled_time text,
  is_timing_point boolean not null default false,
  raw_label text,
  created_at timestamptz not null default now(),
  unique (trip_id, stop_sequence)
);

create index if not exists idx_operations_trip_stops_trip_sequence
  on public.operations_trip_stops(trip_id, stop_sequence);

create index if not exists idx_operations_trip_stops_trip_time
  on public.operations_trip_stops(trip_id, scheduled_time);

create table if not exists public.operations_stop_updates (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  trip_stop_id text not null references public.operations_trip_stops(id) on delete cascade,
  status text,
  actual_time text,
  vehicle_id text,
  notes text,
  updated_by uuid not null references auth.users(id) on delete restrict,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique (trip_stop_id)
);

create index if not exists idx_operations_stop_updates_trip_stop
  on public.operations_stop_updates(trip_stop_id);

alter table public.operations_schedules enable row level security;
alter table public.operations_trips enable row level security;
alter table public.operations_trip_stops enable row level security;
alter table public.operations_stop_updates enable row level security;

drop policy if exists "operations_schedules_select_visible" on public.operations_schedules;
create policy "operations_schedules_select_visible"
  on public.operations_schedules
  for select
  to authenticated
  using (public.can_access_channel(channel_id));

drop policy if exists "operations_schedules_insert_admins" on public.operations_schedules;
create policy "operations_schedules_insert_admins"
  on public.operations_schedules
  for insert
  to authenticated
  with check (
    uploaded_by = auth.uid()
    and exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

drop policy if exists "operations_schedules_update_admins" on public.operations_schedules;
create policy "operations_schedules_update_admins"
  on public.operations_schedules
  for update
  to authenticated
  using (
    exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  )
  with check (
    exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

drop policy if exists "operations_schedules_delete_admins" on public.operations_schedules;
create policy "operations_schedules_delete_admins"
  on public.operations_schedules
  for delete
  to authenticated
  using (
    exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

drop policy if exists "operations_trips_select_visible" on public.operations_trips;
create policy "operations_trips_select_visible"
  on public.operations_trips
  for select
  to authenticated
  using (
    exists (
      select 1
      from public.operations_schedules s
      where s.id = schedule_id
        and public.can_access_channel(s.channel_id)
    )
  );

drop policy if exists "operations_trips_write_admins" on public.operations_trips;
create policy "operations_trips_write_admins"
  on public.operations_trips
  for all
  to authenticated
  using (
    exists (
      select 1
      from public.operations_schedules s
      join public.channels c on c.id = s.channel_id
      where s.id = schedule_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  )
  with check (
    exists (
      select 1
      from public.operations_schedules s
      join public.channels c on c.id = s.channel_id
      where s.id = schedule_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

drop policy if exists "operations_trip_stops_select_visible" on public.operations_trip_stops;
create policy "operations_trip_stops_select_visible"
  on public.operations_trip_stops
  for select
  to authenticated
  using (
    exists (
      select 1
      from public.operations_trips t
      join public.operations_schedules s on s.id = t.schedule_id
      where t.id = trip_id
        and public.can_access_channel(s.channel_id)
    )
  );

drop policy if exists "operations_trip_stops_write_admins" on public.operations_trip_stops;
create policy "operations_trip_stops_write_admins"
  on public.operations_trip_stops
  for all
  to authenticated
  using (
    exists (
      select 1
      from public.operations_trips t
      join public.operations_schedules s on s.id = t.schedule_id
      join public.channels c on c.id = s.channel_id
      where t.id = trip_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  )
  with check (
    exists (
      select 1
      from public.operations_trips t
      join public.operations_schedules s on s.id = t.schedule_id
      join public.channels c on c.id = s.channel_id
      where t.id = trip_id
        and c.type = 'operations'
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

drop policy if exists "operations_stop_updates_select_visible" on public.operations_stop_updates;
create policy "operations_stop_updates_select_visible"
  on public.operations_stop_updates
  for select
  to authenticated
  using (
    exists (
      select 1
      from public.operations_trip_stops ts
      join public.operations_trips t on t.id = ts.trip_id
      join public.operations_schedules s on s.id = t.schedule_id
      where ts.id = trip_stop_id
        and public.can_access_channel(s.channel_id)
    )
  );

drop policy if exists "operations_stop_updates_insert_members" on public.operations_stop_updates;
create policy "operations_stop_updates_insert_members"
  on public.operations_stop_updates
  for insert
  to authenticated
  with check (
    updated_by = auth.uid()
    and exists (
      select 1
      from public.operations_trip_stops ts
      join public.operations_trips t on t.id = ts.trip_id
      join public.operations_schedules s on s.id = t.schedule_id
      where ts.id = trip_stop_id
        and public.can_access_channel(s.channel_id)
    )
  );

drop policy if exists "operations_stop_updates_update_members" on public.operations_stop_updates;
create policy "operations_stop_updates_update_members"
  on public.operations_stop_updates
  for update
  to authenticated
  using (
    exists (
      select 1
      from public.operations_trip_stops ts
      join public.operations_trips t on t.id = ts.trip_id
      join public.operations_schedules s on s.id = t.schedule_id
      where ts.id = trip_stop_id
        and public.can_access_channel(s.channel_id)
    )
  )
  with check (
    updated_by = auth.uid()
    and exists (
      select 1
      from public.operations_trip_stops ts
      join public.operations_trips t on t.id = ts.trip_id
      join public.operations_schedules s on s.id = t.schedule_id
      where ts.id = trip_stop_id
        and public.can_access_channel(s.channel_id)
    )
  );

drop policy if exists "operations_stop_updates_delete_members" on public.operations_stop_updates;
create policy "operations_stop_updates_delete_members"
  on public.operations_stop_updates
  for delete
  to authenticated
  using (
    exists (
      select 1
      from public.operations_trip_stops ts
      join public.operations_trips t on t.id = ts.trip_id
      join public.operations_schedules s on s.id = t.schedule_id
      where ts.id = trip_stop_id
        and public.can_access_channel(s.channel_id)
    )
  );

-- Keep updated_at fresh on edits.
create or replace function public.tg_set_updated_at()
returns trigger
language plpgsql
as $$
begin
  new.updated_at := now();
  return new;
end;
$$;

drop trigger if exists trg_operations_stop_updates_updated_at on public.operations_stop_updates;
create trigger trg_operations_stop_updates_updated_at
before update on public.operations_stop_updates
for each row execute procedure public.tg_set_updated_at();

-- Realtime availability for operations collaboration features.
do $$
begin
  begin
    alter publication supabase_realtime add table public.operations_schedules;
  exception when duplicate_object then null;
  end;
  begin
    alter publication supabase_realtime add table public.operations_trips;
  exception when duplicate_object then null;
  end;
  begin
    alter publication supabase_realtime add table public.operations_trip_stops;
  exception when duplicate_object then null;
  end;
  begin
    alter publication supabase_realtime add table public.operations_stop_updates;
  exception when duplicate_object then null;
  end;
end $$;

