-- Phase 5B: per-conversation mini-games (Tic-Tac-Toe + Vier-Gewinnt /
-- Connect Four).
--
-- conversation_games: one row per game. The bubble in the chat is a
-- regular `messages` row whose plaintext payload is
-- `{v:1, type:'game', game_id:<id>, game_type:<'ttt'|'c4'>}`.
--
-- state JSON shape:
--   ttt:  { kind: 'ttt', board: [null|0|1 × 9] }   (row-major 3×3)
--   c4:   { kind: 'c4',  board: [null|0|1 × 42] }  (row-major 6 rows × 7 cols)
--
-- players JSON: [user_id_a, user_id_b]  — indices 0 and 1 map to board cells.
-- current_turn_user_id alternates; null once finished.
-- winner_user_id null on draw or unfinished.

create table if not exists public.conversation_games (
  id                  uuid primary key default gen_random_uuid(),
  conversation_id     uuid not null references public.conversations(id) on delete cascade,
  game_type           text not null check (game_type in ('ttt', 'c4')),
  state               jsonb not null,
  players             jsonb not null,
  current_turn_user_id uuid null references auth.users(id) on delete set null,
  winner_user_id      uuid null references auth.users(id) on delete set null,
  created_at          timestamptz not null default now(),
  finished_at         timestamptz null
);

create index if not exists conversation_games_conv_idx
  on public.conversation_games(conversation_id, created_at desc);

alter table public.conversation_games enable row level security;

drop policy if exists conversation_games_select on public.conversation_games;
drop policy if exists conversation_games_insert on public.conversation_games;

create policy conversation_games_select
  on public.conversation_games
  for select
  using (public.is_conversation_member(conversation_id));

create policy conversation_games_insert
  on public.conversation_games
  for insert
  with check (
    public.is_conversation_member(conversation_id)
    and current_turn_user_id = auth.uid()
    and (players->>0)::uuid = auth.uid()
  );

alter table public.conversation_games replica identity full;

do $$
begin
  if not exists (
    select 1
      from pg_publication_tables
      where pubname = 'supabase_realtime'
        and schemaname = 'public'
        and tablename = 'conversation_games'
  ) then
    execute 'alter publication supabase_realtime add table public.conversation_games';
  end if;
end
$$;

-- ----------------------------------------------------------------------
-- Pure winner-check + board-full helpers.
-- ----------------------------------------------------------------------

create or replace function public.ttt_check_winner(board jsonb)
returns int
language plpgsql
immutable
as $$
declare
  lines int[][] := array[
    array[0,1,2], array[3,4,5], array[6,7,8],
    array[0,3,6], array[1,4,7], array[2,5,8],
    array[0,4,8], array[2,4,6]
  ];
  ln int[];
  a jsonb;
  b jsonb;
  c jsonb;
begin
  foreach ln slice 1 in array lines loop
    a := board->ln[1];
    b := board->ln[2];
    c := board->ln[3];
    if jsonb_typeof(a) = 'number' and a = b and b = c then
      return (a)::int;
    end if;
  end loop;
  return null;
end;
$$;

create or replace function public.c4_check_winner(board jsonb)
returns int
language plpgsql
immutable
as $$
declare
  rows constant int := 6;
  cols constant int := 7;
  r int;
  c int;
  i int;
  d_r int;
  d_c int;
  directions int[][] := array[
    array[0, 1],
    array[1, 0],
    array[1, 1],
    array[1, -1]
  ];
  dir int[];
  base jsonb;
  cell jsonb;
  ok boolean;
begin
  for r in 0..rows-1 loop
    for c in 0..cols-1 loop
      base := board->(r * cols + c);
      if jsonb_typeof(base) <> 'number' then continue; end if;
      foreach dir slice 1 in array directions loop
        d_r := dir[1];
        d_c := dir[2];
        if r + d_r * 3 < 0 or r + d_r * 3 >= rows then continue; end if;
        if c + d_c * 3 < 0 or c + d_c * 3 >= cols then continue; end if;
        ok := true;
        for i in 1..3 loop
          cell := board->((r + d_r * i) * cols + (c + d_c * i));
          if cell is null or cell <> base then ok := false; exit; end if;
        end loop;
        if ok then return (base)::int; end if;
      end loop;
    end loop;
  end loop;
  return null;
end;
$$;

create or replace function public.board_is_full(board jsonb)
returns boolean
language sql
immutable
as $$
  select not exists (
    select 1 from jsonb_array_elements(board) elt where jsonb_typeof(elt) is distinct from 'number'
  );
$$;

-- ----------------------------------------------------------------------
-- The state-machine RPC.
-- ----------------------------------------------------------------------

create or replace function public.game_make_move(p_game_id uuid, p_move jsonb)
returns jsonb
language plpgsql
security definer
set search_path = public
as $$
declare
  v_game record;
  v_state jsonb;
  v_board jsonb;
  v_kind text;
  v_player_idx int;
  v_other_user uuid;
  v_winner_idx int;
  v_finished boolean;
  v_cell int;
  v_col int;
  v_row int;
  v_cols constant int := 7;
  v_rows constant int := 6;
  v_target_row int;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated' using errcode = '28000';
  end if;

  select * into v_game from public.conversation_games where id = p_game_id for update;
  if not found then
    raise exception 'game_not_found' using errcode = 'P0002';
  end if;
  if v_game.finished_at is not null then
    raise exception 'game_finished' using errcode = '42501';
  end if;
  if v_game.current_turn_user_id is null
     or v_game.current_turn_user_id <> auth.uid() then
    raise exception 'not_your_turn' using errcode = '42501';
  end if;

  if (v_game.players->>0)::uuid = auth.uid() then
    v_player_idx := 0;
    v_other_user := (v_game.players->>1)::uuid;
  elsif (v_game.players->>1)::uuid = auth.uid() then
    v_player_idx := 1;
    v_other_user := (v_game.players->>0)::uuid;
  else
    raise exception 'not_a_player' using errcode = '42501';
  end if;

  v_state := v_game.state;
  v_kind := v_state->>'kind';
  v_board := v_state->'board';

  if v_kind = 'ttt' then
    if (p_move->'cell') is null or jsonb_typeof(p_move->'cell') <> 'number' then
      raise exception 'bad_move' using errcode = '22023';
    end if;
    v_cell := (p_move->>'cell')::int;
    if v_cell < 0 or v_cell > 8 then
      raise exception 'bad_move' using errcode = '22023';
    end if;
    if jsonb_typeof(v_board->v_cell) = 'number' then
      raise exception 'cell_taken' using errcode = '42501';
    end if;
    v_board := jsonb_set(v_board, array[v_cell::text], to_jsonb(v_player_idx));
    v_winner_idx := public.ttt_check_winner(v_board);

  elsif v_kind = 'c4' then
    if (p_move->'column') is null or jsonb_typeof(p_move->'column') <> 'number' then
      raise exception 'bad_move' using errcode = '22023';
    end if;
    v_col := (p_move->>'column')::int;
    if v_col < 0 or v_col >= v_cols then
      raise exception 'bad_move' using errcode = '22023';
    end if;
    v_target_row := -1;
    for v_row in reverse v_rows - 1 .. 0 loop
      if jsonb_typeof(v_board->(v_row * v_cols + v_col)) is distinct from 'number' then
        v_target_row := v_row;
        exit;
      end if;
    end loop;
    if v_target_row < 0 then
      raise exception 'column_full' using errcode = '42501';
    end if;
    v_board := jsonb_set(v_board, array[(v_target_row * v_cols + v_col)::text], to_jsonb(v_player_idx));
    v_winner_idx := public.c4_check_winner(v_board);

  else
    raise exception 'unknown_game_kind' using errcode = '42501';
  end if;

  v_state := jsonb_set(v_state, '{board}', v_board);
  v_finished := v_winner_idx is not null or public.board_is_full(v_board);

  update public.conversation_games
    set state = v_state,
        current_turn_user_id = case when v_finished then null else v_other_user end,
        winner_user_id = case when v_winner_idx is not null then (v_game.players->>v_winner_idx)::uuid else null end,
        finished_at = case when v_finished then now() else null end
    where id = p_game_id;

  return v_state;
end;
$$;

revoke all on function public.game_make_move(uuid, jsonb) from public;
grant execute on function public.game_make_move(uuid, jsonb) to authenticated;
