begin;

-- Dateless occupation_note (and other background notes) can be confirmed
-- without a calendar date. Career dated events still do not cover occupation.

create or replace function public.agentic_rectification_allows_dateless_confirm(p_kind text)
returns boolean
language sql
immutable
as $$
  select p_kind in ('occupation_note', 'appearance_note', 'birthmark_or_scar')
$$;

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

create or replace function public.record_agentic_rectification_evidence_batch(
  p_user_id uuid,
  p_case_id uuid,
  p_source_turn_id uuid,
  p_focus_id uuid,
  p_items jsonb
)
returns jsonb
language plpgsql
security definer
set search_path = ''
as $$
declare
  v_case public.agentic_rectification_cases%rowtype;
  v_turn public.agentic_rectification_turns%rowtype;
  v_focus public.agentic_rectification_conversation_focuses%rowtype;
  v_focus_replay boolean := false;
  v_focus_match_id uuid;
  v_focus_match_count integer := 0;
  v_focus_resolution text := 'not_requested';
  v_item jsonb;
  v_index bigint;
  v_item_key text;
  v_quote text;
  v_subject text;
  v_kind text;
  v_domain text;
  v_precision text;
  v_summary text;
  v_from date;
  v_to date;
  v_status text;
  v_outcome text;
  v_error text;
  v_clarification jsonb;
  v_existing public.agentic_rectification_evidence%rowtype;
  v_evidence_id uuid;
  v_idempotent boolean;
  v_results jsonb := '[]'::jsonb;
  v_accepted integer := 0;
  v_needs integer := 0;
  v_rejected integer := 0;
begin
  if p_user_id is null or p_case_id is null or p_source_turn_id is null
    or p_items is null or jsonb_typeof(p_items) <> 'array'
    or jsonb_array_length(p_items) = 0 or jsonb_array_length(p_items) > 12 then
    raise exception 'agentic_rectification_invalid_input' using errcode = 'P0001';
  end if;

  select * into v_case
  from public.agentic_rectification_cases
  where id = p_case_id and user_id = p_user_id
  for update;
  if not found then
    raise exception 'agentic_rectification_case_not_found' using errcode = 'P0001';
  end if;
  if v_case.status in ('confirmed', 'closed', 'abandoned', 'superseded') then
    raise exception 'agentic_rectification_case_terminal' using errcode = 'P0001';
  end if;

  select * into v_turn
  from public.agentic_rectification_turns
  where id = p_source_turn_id and case_id = p_case_id;
  if not found then
    raise exception 'agentic_rectification_turn_not_found' using errcode = 'P0001';
  end if;
  if v_turn.user_message is null then
    raise exception 'agentic_rectification_quote_not_grounded' using errcode = 'P0001';
  end if;

  if p_focus_id is not null then
    select * into v_focus
    from public.agentic_rectification_conversation_focuses
    where id = p_focus_id and case_id = p_case_id
    for update;
    if not found or v_focus.status not in ('active', 'resolved') then
      raise exception 'agentic_rectification_focus_not_active' using errcode = 'P0001';
    end if;
    v_focus_replay := v_focus.status = 'resolved';
    if not v_focus_replay and v_focus.target_evidence_id is not null then
      raise exception 'agentic_rectification_focus_target_mismatch' using errcode = 'P0001';
    end if;
    v_focus_resolution := case when v_focus_replay then 'idempotent' else 'active' end;
    if v_focus_replay and exists (
      select 1
      from jsonb_array_elements(p_items) item
      where length(btrim(coalesce(item->>'idempotency_key', ''))) = 0
        or not exists (
          select 1 from public.agentic_rectification_evidence e
          where e.case_id = p_case_id
            and e.idempotency_key = btrim(item->>'idempotency_key')
        )
    ) then
      raise exception 'agentic_rectification_focus_not_active' using errcode = 'P0001';
    end if;
  end if;

  for v_item, v_index in
    select value, ordinality
    from jsonb_array_elements(p_items) with ordinality
  loop
    v_item_key := btrim(coalesce(v_item->>'idempotency_key', ''));
    v_quote := btrim(coalesce(v_item->>'quote', ''));
    v_subject := coalesce(v_item->>'subject', '');
    v_kind := coalesce(v_item->>'event_kind', '');
    v_domain := coalesce(v_item->>'domain', '');
    v_precision := coalesce(v_item->>'date_precision', '');
    v_summary := btrim(coalesce(v_item->>'summary', ''));
    v_from := null;
    v_to := null;
    v_error := null;
    v_clarification := '[]'::jsonb;
    v_evidence_id := null;
    v_idempotent := false;

    begin
      if nullif(v_item->>'occurred_from', '') is not null then
        v_from := (v_item->>'occurred_from')::date;
      end if;
      if nullif(v_item->>'occurred_to', '') is not null then
        v_to := (v_item->>'occurred_to')::date;
      end if;
    exception when others then
      v_error := 'invalid_date';
    end;

    if v_error is null and (
      length(v_item_key) = 0 or length(v_item_key) > 160
      or length(v_quote) = 0 or length(v_summary) = 0
      or v_subject not in ('self', 'family', 'other')
      or not (v_kind = any (public.agentic_rectification_evidence_kinds()))
      or not (v_domain = any (public.agentic_rectification_evidence_domains()))
      or not (v_precision = any (public.agentic_rectification_date_precisions()))
    ) then
      v_error := 'invalid_item';
    end if;

    if v_error is null and position(
      public.agentic_rectification_normalize_quote(v_quote)
      in public.agentic_rectification_normalize_quote(v_turn.user_message)
    ) = 0 then
      v_error := 'quote_not_grounded';
    end if;

    if v_error is null and v_precision = 'range'
      and (v_from is null or v_to is null or v_from > v_to) then
      v_error := 'invalid_range';
    end if;

    if v_error is not null then
      v_outcome := 'rejected';
      v_status := 'rejected';
      v_rejected := v_rejected + 1;
    else
      select * into v_existing
      from public.agentic_rectification_evidence
      where case_id = p_case_id and idempotency_key = v_item_key;

      if found then
        if v_existing.source_turn_id is distinct from p_source_turn_id
          or v_existing.user_quote is distinct from v_quote
          or v_existing.subject is distinct from v_subject
          or v_existing.event_kind is distinct from v_kind
          or v_existing.domain is distinct from v_domain
          or v_existing.occurred_from is distinct from v_from
          or v_existing.occurred_to is distinct from v_to
          or v_existing.date_precision is distinct from v_precision
          or v_existing.summary is distinct from v_summary then
          v_outcome := 'rejected';
          v_status := 'rejected';
          v_error := 'idempotency_conflict';
          v_rejected := v_rejected + 1;
        else
          v_evidence_id := v_existing.id;
          v_idempotent := true;
          if v_existing.status = 'confirmed' then
            v_outcome := 'accepted';
            v_status := 'confirmed';
            v_accepted := v_accepted + 1;
          elsif public.agentic_rectification_allows_dateless_confirm(v_kind)
            and (v_precision = 'unknown' or v_from is null) then
            update public.agentic_rectification_evidence
            set status = 'confirmed',
                confirmed_at = coalesce(confirmed_at, pg_catalog.now()),
                updated_at = pg_catalog.now()
            where id = v_existing.id
              and status is distinct from 'confirmed';
            v_outcome := 'accepted';
            v_status := 'confirmed';
            v_accepted := v_accepted + 1;
          else
            v_outcome := 'needs_clarification';
            v_status := v_existing.status;
            v_clarification := case
              when v_existing.date_precision = 'unknown' then '["date"]'::jsonb
              else '[]'::jsonb
            end;
            v_needs := v_needs + 1;
          end if;
        end if;
      else
        if (v_precision = 'unknown' or v_from is null)
          and not public.agentic_rectification_allows_dateless_confirm(v_kind) then
          v_outcome := 'needs_clarification';
          v_status := 'draft';
          v_clarification := '["date"]'::jsonb;
          v_needs := v_needs + 1;
        else
          v_outcome := 'accepted';
          v_status := 'confirmed';
          v_accepted := v_accepted + 1;
        end if;

        insert into public.agentic_rectification_evidence (
          case_id, source_turn_id, user_quote, subject, event_kind, domain,
          occurred_from, occurred_to, date_precision, summary, status,
          confirmed_at, idempotency_key
        ) values (
          p_case_id, p_source_turn_id, v_quote, v_subject, v_kind, v_domain,
          v_from, v_to, v_precision, v_summary, v_status,
          case when v_status = 'confirmed' then pg_catalog.now() else null end,
          v_item_key
        ) returning id into v_evidence_id;
      end if;
    end if;

    if p_focus_id is not null
      and v_outcome = 'accepted'
      and v_evidence_id is not null
      and (v_focus.target_domain is null or v_domain = v_focus.target_domain)
      and (v_focus.target_kind is null or v_kind = v_focus.target_kind) then
      v_focus_match_count := v_focus_match_count + 1;
      if v_focus_match_id is null then
        v_focus_match_id := v_evidence_id;
      end if;
    end if;

    v_results := v_results || jsonb_build_array(jsonb_build_object(
      'index', v_index - 1,
      'idempotency_key', nullif(v_item_key, ''),
      'outcome', v_outcome,
      'evidence_id', v_evidence_id,
      'status', v_status,
      'idempotent', v_idempotent,
      'clarification_fields', v_clarification,
      'error_code', v_error
    ));
  end loop;

  update public.agentic_rectification_cases
  set status = case when status = 'draft' and v_accepted > 0 then 'collecting_evidence' else status end,
      last_activity_at = pg_catalog.now(),
      updated_at = pg_catalog.now()
  where id = p_case_id;

  if p_focus_id is not null then
    if v_focus_replay then
      if v_focus_match_count <> 1
        or v_focus_match_id is distinct from v_focus.target_evidence_id then
        raise exception 'agentic_rectification_focus_idempotency_conflict' using errcode = 'P0001';
      end if;
      v_focus_resolution := 'idempotent';
    elsif v_focus_match_count = 1 then
      perform public.resolve_agentic_rectification_conversation_focus(
        p_user_id, p_case_id, p_focus_id, 'resolved', v_focus_match_id
      );
      v_focus_resolution := 'resolved';
    elsif v_focus_match_count > 1 then
      v_focus_resolution := 'ambiguous';
    else
      v_focus_resolution := 'unmatched';
    end if;
  end if;

  return jsonb_build_object(
    'items', v_results,
    'accepted_count', v_accepted,
    'needs_clarification_count', v_needs,
    'rejected_count', v_rejected,
    'focus_id', p_focus_id,
    'focus_evidence_id', v_focus_match_id,
    'focus_resolution', v_focus_resolution
  );
end;
$$;

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

update public.agentic_rectification_evidence
set status = 'confirmed',
    confirmed_at = coalesce(confirmed_at, pg_catalog.now()),
    updated_at = pg_catalog.now()
where event_kind in ('occupation_note', 'appearance_note', 'birthmark_or_scar')
  and status in ('draft', 'pending_confirmation')
  and (date_precision = 'unknown' or occurred_from is null);

commit;
