-- Fresh reset migration: replace collaboration IDs with lowercase hash-like text IDs.
-- No compatibility shims by design.

create extension if not exists pgcrypto;

-- Drop existing collaboration objects.
drop table if exists public.messages cascade;
drop table if exists public.channel_members cascade;
drop table if exists public.channels cascade;
drop table if exists public.organization_members cascade;
drop table if exists public.organizations cascade;

drop function if exists public.can_access_channel(uuid, uuid);
drop function if exists public.org_role(uuid, uuid);
drop function if exists public.is_org_member(uuid, uuid);
drop function if exists public.can_access_channel(text, uuid);
drop function if exists public.org_role(text, uuid);
drop function if exists public.is_org_member(text, uuid);

-- Keep enum types if already present.
do $$
begin
  if not exists (select 1 from pg_type where typname = 'organization_role') then
    create type public.organization_role as enum ('owner', 'admin', 'member');
  end if;
  if not exists (select 1 from pg_type where typname = 'channel_type') then
    create type public.channel_type as enum ('text', 'voice', 'announcement');
  end if;
end $$;

create or replace function public.gen_hash_id()
returns text
language sql
volatile
as $$
  select substring(md5(random()::text || clock_timestamp()::text) from 1 for 16);
$$;

create table public.organizations (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  name text not null,
  slug text not null unique,
  owner_user_id uuid not null references auth.users(id) on delete restrict,
  created_at timestamptz not null default now()
);

create table public.organization_members (
  organization_id text not null references public.organizations(id) on delete cascade,
  user_id uuid not null references auth.users(id) on delete cascade,
  role public.organization_role not null default 'member',
  joined_at timestamptz not null default now(),
  primary key (organization_id, user_id)
);

create table public.channels (
  id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
  organization_id text not null references public.organizations(id) on delete cascade,
  name text not null,
  slug text not null,
  type public.channel_type not null default 'text',
  position integer not null default 0,
  topic text,
  is_private boolean not null default false,
  created_by uuid not null references auth.users(id) on delete restrict,
  created_at timestamptz not null default now(),
  unique (organization_id, slug)
);

create table public.channel_members (
  channel_id text not null references public.channels(id) on delete cascade,
  user_id uuid not null references auth.users(id) on delete cascade,
  joined_at timestamptz not null default now(),
  primary key (channel_id, user_id)
);

create table public.messages (
  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,
  author_user_id uuid not null references auth.users(id) on delete restrict,
  content text not null check (char_length(content) <= 4000),
  created_at timestamptz not null default now(),
  edited_at timestamptz,
  deleted_at timestamptz
);

create index idx_org_members_user on public.organization_members(user_id);
create index idx_channels_org_position on public.channels(organization_id, position);
create index idx_messages_channel_created_at_desc on public.messages(channel_id, created_at desc);

create or replace function public.is_org_member(org_id text, uid uuid default auth.uid())
returns boolean
language sql
stable
security definer
set search_path = public
as $$
  select exists (
    select 1
    from public.organization_members om
    where om.organization_id = org_id
      and om.user_id = uid
  );
$$;

create or replace function public.org_role(org_id text, uid uuid default auth.uid())
returns public.organization_role
language sql
stable
security definer
set search_path = public
as $$
  select om.role
  from public.organization_members om
  where om.organization_id = org_id
    and om.user_id = uid
  limit 1;
$$;

create or replace function public.can_access_channel(ch_id text, uid uuid default auth.uid())
returns boolean
language sql
stable
security definer
set search_path = public
as $$
  select exists (
    select 1
    from public.channels c
    where c.id = ch_id
      and public.is_org_member(c.organization_id, uid)
      and (
        c.is_private = false
        or exists (
          select 1
          from public.channel_members cm
          where cm.channel_id = c.id
            and cm.user_id = uid
        )
      )
  );
$$;

alter table public.organizations enable row level security;
alter table public.organization_members enable row level security;
alter table public.channels enable row level security;
alter table public.channel_members enable row level security;
alter table public.messages enable row level security;

create policy "organizations_select_members"
  on public.organizations
  for select
  to authenticated
  using (public.is_org_member(id));

create policy "organizations_insert_owner"
  on public.organizations
  for insert
  to authenticated
  with check (owner_user_id = auth.uid());

create policy "organizations_update_admins"
  on public.organizations
  for update
  to authenticated
  using (public.org_role(id) in ('owner', 'admin'))
  with check (public.org_role(id) in ('owner', 'admin'));

create policy "organization_members_select_members"
  on public.organization_members
  for select
  to authenticated
  using (public.is_org_member(organization_id));

create policy "organization_members_insert_admins"
  on public.organization_members
  for insert
  to authenticated
  with check (
    user_id = auth.uid()
    and (
      public.org_role(organization_id) in ('owner', 'admin')
      or exists (
        select 1
        from public.organizations o
        where o.id = organization_id
          and o.owner_user_id = auth.uid()
      )
    )
  );

create policy "organization_members_update_admins"
  on public.organization_members
  for update
  to authenticated
  using (public.org_role(organization_id) in ('owner', 'admin'))
  with check (public.org_role(organization_id) in ('owner', 'admin'));

create policy "organization_members_delete_admins"
  on public.organization_members
  for delete
  to authenticated
  using (public.org_role(organization_id) in ('owner', 'admin'));

create policy "channels_select_visible"
  on public.channels
  for select
  to authenticated
  using (public.can_access_channel(id));

create policy "channels_insert_admins"
  on public.channels
  for insert
  to authenticated
  with check (
    public.org_role(organization_id) in ('owner', 'admin')
    and created_by = auth.uid()
  );

create policy "channels_update_admins"
  on public.channels
  for update
  to authenticated
  using (public.org_role(organization_id) in ('owner', 'admin'))
  with check (public.org_role(organization_id) in ('owner', 'admin'));

create policy "channels_delete_admins"
  on public.channels
  for delete
  to authenticated
  using (public.org_role(organization_id) in ('owner', 'admin'));

create policy "channel_members_select_visible"
  on public.channel_members
  for select
  to authenticated
  using (
    exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and public.is_org_member(c.organization_id)
    )
  );

create policy "channel_members_insert_admins"
  on public.channel_members
  for insert
  to authenticated
  with check (
    exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

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

create policy "messages_select_visible_channel"
  on public.messages
  for select
  to authenticated
  using (public.can_access_channel(channel_id));

create policy "messages_insert_visible_channel"
  on public.messages
  for insert
  to authenticated
  with check (
    public.can_access_channel(channel_id)
    and author_user_id = auth.uid()
    and deleted_at is null
  );

create policy "messages_update_author_or_admin"
  on public.messages
  for update
  to authenticated
  using (
    author_user_id = auth.uid()
    or exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  )
  with check (
    author_user_id = auth.uid()
    or exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );

create policy "messages_delete_author_or_admin"
  on public.messages
  for delete
  to authenticated
  using (
    author_user_id = auth.uid()
    or exists (
      select 1
      from public.channels c
      where c.id = channel_id
        and public.org_role(c.organization_id) in ('owner', 'admin')
    )
  );
