alter table public.birth_time_rectification_cases
  add column if not exists journey_protocol text not null default 'legacy-guided-v1';

alter table public.birth_time_rectification_cases
  drop constraint if exists birth_time_rectification_cases_journey_protocol_check,
  add constraint birth_time_rectification_cases_journey_protocol_check
    check (journey_protocol in ('legacy-guided-v1', 'dynamic-choice-v2'));

create or replace function public.birth_time_dynamic_agent_context_valid(value jsonb)
returns boolean
language sql
immutable
strict
set search_path = ''
as $$
  select pg_catalog.jsonb_typeof(value) = 'array'
    and not pg_catalog.jsonb_path_exists(value, '$[*] ? (@.type() != "string")')
    and not exists (
      select 1
      from pg_catalog.jsonb_array_elements_text(value) as item(note)
      where pg_catalog.length(note) > 240
    );
$$;

create table if not exists public.birth_time_rectification_dynamic_state (
  case_id uuid primary key references public.birth_time_rectification_cases(id) on delete cascade,
  user_id uuid not null references auth.users(id) on delete cascade,
  candidate_model jsonb,
  current_choice_question jsonb,
  choice_answers jsonb not null default '[]'::jsonb,
  choice_evidence jsonb not null default '[]'::jsonb,
  dynamic_control jsonb not null,
  agent_context jsonb not null default '[]'::jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  check (candidate_model is null or jsonb_typeof(candidate_model) = 'object'),
  check (current_choice_question is null or jsonb_typeof(current_choice_question) = 'object'),
  check (jsonb_typeof(choice_answers) = 'array' and jsonb_array_length(choice_answers) <= 50),
  check (jsonb_typeof(choice_evidence) = 'array' and jsonb_array_length(choice_evidence) <= 10),
  check (jsonb_typeof(dynamic_control) = 'object'),
  check (jsonb_typeof(agent_context) = 'array' and jsonb_array_length(agent_context) <= 10),
  check (public.birth_time_dynamic_agent_context_valid(agent_context))
);

alter table public.birth_time_rectification_dynamic_state enable row level security;
revoke all on table public.birth_time_rectification_dynamic_state from anon, authenticated;
revoke all on table public.birth_time_rectification_dynamic_state from service_role;
grant all on table public.birth_time_rectification_dynamic_state to service_role;

revoke all on function public.birth_time_dynamic_agent_context_valid(jsonb) from public, anon, authenticated;
grant execute on function public.birth_time_dynamic_agent_context_valid(jsonb) to service_role;

create or replace function public.persist_birth_time_dynamic_private_state(
  p_case_id uuid,
  p_user_id uuid,
  p_private_state jsonb
)
returns void
language plpgsql
set search_path = ''
as $$
begin
  if pg_catalog.jsonb_typeof(p_private_state) is distinct from 'object' then
    raise exception 'birth_time_dynamic_private_state_invalid';
  end if;
  insert into public.birth_time_rectification_dynamic_state (
    case_id, user_id, candidate_model, current_choice_question,
    choice_answers, choice_evidence, dynamic_control, agent_context, updated_at
  ) values (
    p_case_id, p_user_id,
    nullif(p_private_state -> 'candidateModel', 'null'::jsonb),
    nullif(p_private_state -> 'currentChoiceQuestion', 'null'::jsonb),
    coalesce(p_private_state -> 'choiceAnswers', '[]'::jsonb),
    coalesce(p_private_state -> 'choiceEvidence', '[]'::jsonb),
    p_private_state -> 'dynamicControl',
    coalesce(p_private_state -> 'agentContext', '[]'::jsonb), now()
  ) on conflict (case_id) do update set
    user_id = excluded.user_id,
    candidate_model = excluded.candidate_model,
    current_choice_question = excluded.current_choice_question,
    choice_answers = excluded.choice_answers,
    choice_evidence = excluded.choice_evidence,
    dynamic_control = excluded.dynamic_control,
    agent_context = excluded.agent_context,
    updated_at = excluded.updated_at;
end;
$$;

create or replace function public.create_birth_time_dynamic_case(
  p_user_id uuid,
  p_public_case jsonb,
  p_private_state jsonb,
  p_profile jsonb
)
returns uuid
language plpgsql
security definer
set search_path = ''
as $$
declare
  v_case_id uuid;
begin
  if jsonb_typeof(p_public_case) is distinct from 'object'
    or jsonb_typeof(p_private_state) is distinct from 'object'
    or jsonb_typeof(p_profile) is distinct from 'object'
    or p_public_case ->> 'journeyProtocol' is distinct from 'dynamic-choice-v2'
    or p_public_case #>> '{turnState,journeyProtocol}' is distinct from 'dynamic-choice-v2'
    or (p_public_case #>> '{turnState,turnVersion}')::bigint is distinct from 0
    or jsonb_path_exists(p_public_case, '$.**.partitionId')
    or jsonb_path_exists(p_public_case, '$.**.candidateScores')
    or jsonb_path_exists(p_public_case, '$.**.agentContext') then
    raise exception 'birth_time_dynamic_case_invalid';
  end if;

  insert into public.birth_time_rectification_cases (
    user_id, journey_protocol, status, reported_date, reported_time,
    reported_period, source, uncertainty_before_minutes,
    uncertainty_after_minutes, questionnaire, journey_snapshot,
    candidate_scan, turn_state, candidate_start, candidate_end,
    confirmed_time, confirmed_at
  ) values (
    p_user_id, 'dynamic-choice-v2', p_public_case ->> 'status',
    (p_public_case ->> 'reportedDate')::date,
    nullif(p_public_case ->> 'reportedTime', '')::time,
    nullif(p_public_case ->> 'reportedPeriod', ''),
    p_public_case ->> 'source',
    nullif(p_public_case ->> 'uncertaintyBeforeMinutes', '')::integer,
    nullif(p_public_case ->> 'uncertaintyAfterMinutes', '')::integer,
    coalesce(p_public_case -> 'questionnaire', '{}'::jsonb),
    p_public_case -> 'journeySnapshot',
    coalesce(p_public_case -> 'candidateScan', '{}'::jsonb),
    p_public_case -> 'turnState',
    nullif(p_public_case ->> 'candidateStart', '')::time,
    nullif(p_public_case ->> 'candidateEnd', '')::time,
    nullif(p_public_case ->> 'confirmedTime', '')::time,
    nullif(p_public_case ->> 'confirmedAt', '')::timestamptz
  ) returning id into v_case_id;

  perform public.persist_birth_time_dynamic_private_state(
    v_case_id, p_user_id, p_private_state
  );

  update public.profiles
  set reported_birth_time = nullif(p_profile ->> 'reportedBirthTime', '')::time,
      birth_time_source = p_profile ->> 'birthTimeSource',
      birth_time_period = nullif(p_profile ->> 'birthTimePeriod', ''),
      birth_time_clue = nullif(p_profile ->> 'birthTimeClue', ''),
      uncertainty_before_minutes = nullif(p_profile ->> 'uncertaintyBeforeMinutes', '')::integer,
      uncertainty_after_minutes = nullif(p_profile ->> 'uncertaintyAfterMinutes', '')::integer,
      birth_time_status = p_profile ->> 'birthTimeStatus',
      rectification_confidence = null,
      rectification_case_id = v_case_id
  where id = p_user_id;
  if not found then
    raise exception 'birth_time_dynamic_profile_not_found';
  end if;
  return v_case_id;
end;
$$;

create or replace function public.save_birth_time_dynamic_turn(
  p_user_id uuid,
  p_case_id uuid,
  p_expected_version bigint,
  p_action_id uuid,
  p_public_turn_state jsonb,
  p_snapshot jsonb,
  p_candidate_result jsonb,
  p_private_state jsonb
)
returns bigint
language plpgsql
security definer
set search_path = ''
as $$
declare
  v_case public.birth_time_rectification_cases%rowtype;
  v_new_version bigint;
begin
  select c.* into v_case
  from public.birth_time_rectification_cases c
  where c.id = p_case_id and c.user_id = p_user_id
  for update;
  if not found or v_case.journey_protocol is distinct from 'dynamic-choice-v2' then
    raise exception 'birth_time_dynamic_case_not_found';
  end if;
  if p_action_id = any(v_case.processed_action_ids) then
    return v_case.turn_version;
  end if;
  if v_case.turn_version is distinct from p_expected_version then
    raise exception 'stale_birth_time_dynamic_turn';
  end if;
  if p_public_turn_state ->> 'journeyProtocol' is distinct from 'dynamic-choice-v2'
    or (p_public_turn_state ->> 'turnVersion')::bigint is distinct from p_expected_version + 1
    or jsonb_typeof(p_private_state) is distinct from 'object'
    or jsonb_path_exists(p_public_turn_state, '$.**.partitionId')
    or jsonb_path_exists(p_public_turn_state, '$.**.candidateScores')
    or jsonb_path_exists(p_public_turn_state, '$.**.agentContext') then
    raise exception 'birth_time_dynamic_turn_invalid';
  end if;

  update public.birth_time_rectification_cases
  set status = case p_snapshot ->> 'state'
        when 'ready' then 'confirmed'
        when 'confirming' then 'confirming'
        when 'candidate' then 'candidate'
        else 'rectifying'
      end,
      journey_snapshot = p_snapshot,
      candidate_result = coalesce(p_candidate_result, '{}'::jsonb),
      event_scoring_version = p_candidate_result ->> 'algorithmVersion',
      candidate_result_id = case
        when p_candidate_result ? 'resultId' then (p_candidate_result ->> 'resultId')::uuid
        else null
      end,
      candidate_start = case
        when p_candidate_result #>> '{winningSegment,startTime}' is null then null
        else (p_candidate_result #>> '{winningSegment,startTime}')::time
      end,
      candidate_end = case
        when p_candidate_result #>> '{winningSegment,endTime}' is null then null
        else (p_candidate_result #>> '{winningSegment,endTime}')::time
      end,
      turn_version = p_expected_version + 1,
      turn_state = p_public_turn_state,
      evidence_draft = null,
      processed_action_ids = case
        when cardinality(processed_action_ids) >= 100
          then processed_action_ids[2:100] || p_action_id
        else processed_action_ids || p_action_id
      end,
      updated_at = now()
  where id = p_case_id and user_id = p_user_id and turn_version = p_expected_version
  returning turn_version into v_new_version;
  if v_new_version is null then
    raise exception 'stale_birth_time_dynamic_turn';
  end if;

  perform public.persist_birth_time_dynamic_private_state(
    p_case_id, p_user_id, p_private_state
  );
  return v_new_version;
end;
$$;


revoke all on function public.create_birth_time_dynamic_case(uuid, jsonb, jsonb, jsonb) from public, anon, authenticated;
revoke all on function public.persist_birth_time_dynamic_private_state(uuid, uuid, jsonb) from public, anon, authenticated, service_role;
revoke all on function public.save_birth_time_dynamic_turn(uuid, uuid, bigint, uuid, jsonb, jsonb, jsonb, jsonb) from public, anon, authenticated;

grant execute on function public.create_birth_time_dynamic_case(uuid, jsonb, jsonb, jsonb) to service_role;
grant execute on function public.save_birth_time_dynamic_turn(uuid, uuid, bigint, uuid, jsonb, jsonb, jsonb, jsonb) to service_role;
