-- Make a newly launched campaign playable immediately: the owner receives a
-- human-controlled starter and the party receives one persistent AI companion.

create or replace function public.stage_two_create_campaign(
  p_world_id uuid,
  p_owner_id uuid,
  p_title text
) returns uuid language plpgsql security definer set search_path = '' as $$
declare
  v_world public.worlds%rowtype;
  v_campaign_id uuid;
  v_owner_name text;
begin
  if p_title is null or char_length(btrim(p_title)) not between 3 and 100 then
    raise exception 'campaign title must be between 3 and 100 characters';
  end if;
  select * into v_world from public.worlds
  where id = p_world_id and owner_id = p_owner_id and status = 'confirmed' for share;
  if not found then raise exception 'confirmed world not found'; end if;

  select nullif(btrim(display_name), '') into v_owner_name
  from public.profiles where id = p_owner_id;
  v_owner_name := left(coalesce(v_owner_name, 'Wayfinder'), 80);

  insert into public.campaigns(world_id, owner_id, title, current_scene, next_prompt, status)
  values (p_world_id, p_owner_id, btrim(p_title), coalesce(v_world.opening_scene, v_world.premise), 'What do you do?', 'active')
  returning id into v_campaign_id;
  insert into public.campaign_members(campaign_id, user_id, role)
  values (v_campaign_id, p_owner_id, 'owner');

  insert into public.characters(
    campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
    defense, proficiency, inventory, statuses, persona
  ) values (
    v_campaign_id,
    p_owner_id,
    v_owner_name,
    left('An adaptable protagonist ready to confront the opening mystery of ' || v_world.title || '.', 600),
    'human'::public.character_controller,
    '{"str":10,"dex":12,"con":12,"int":11,"wis":13,"cha":10}'::jsonb,
    12,
    12,
    12,
    2,
    '["Field kit","Personal keepsake"]'::jsonb,
    '[]'::jsonb,
    jsonb_build_object(
      'voice', 'Defined by the player.',
      'motivation', 'Discover what the opening scene is hiding.',
      'flaw', 'Still learning what this world demands.',
      'bond', 'Protect the party through the first danger.'
    )
  );

  insert into public.characters(
    campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
    defense, proficiency, inventory, statuses, persona
  ) values (
    v_campaign_id,
    null,
    'Echo',
    left('A persistent AI companion shaped by the ' || v_world.genre || ' world of ' || v_world.title || ', ready to support the player without taking over their choices.', 600),
    'ai'::public.character_controller,
    '{"str":9,"dex":13,"con":11,"int":13,"wis":12,"cha":10}'::jsonb,
    11,
    11,
    13,
    2,
    '["Survey kit","Emergency supplies"]'::jsonb,
    '[]'::jsonb,
    jsonb_build_object(
      'voice', 'Observant, concise, and quietly curious.',
      'motivation', 'Help the party understand this unfamiliar world.',
      'flaw', 'Sometimes values patterns more than instinct.',
      'bond', 'Stays beside the player when the path turns dangerous.'
    )
  );

  insert into public.rounds(campaign_id, number) values (v_campaign_id, 1);
  return v_campaign_id;
end;
$$;

-- Members who have not created a character cannot submit an intent and must not
-- hold the round open. Only active members with a human-controlled character
-- participate in the readiness barrier.
create or replace function public.stage_two_submit_intent(
  p_round_id uuid,
  p_user_id uuid,
  p_character_id uuid,
  p_action text,
  p_ready boolean default false
) returns jsonb language plpgsql security definer set search_path = '' as $$
declare
  v_round public.rounds%rowtype;
  v_member public.campaign_members%rowtype;
  v_intent_id uuid;
  v_job_id uuid;
begin
  if p_action is null or char_length(btrim(p_action)) not between 1 and 2000 then
    raise exception 'action must be between 1 and 2000 characters';
  end if;
  select * into v_round from public.rounds where id = p_round_id for update;
  if not found or v_round.status <> 'open' then raise exception 'round is not open'; end if;
  select * into v_member from public.campaign_members
  where campaign_id = v_round.campaign_id and user_id = p_user_id and active;
  if not found then raise exception 'active campaign membership not found'; end if;
  if not exists (
    select 1 from public.characters
    where id = p_character_id and campaign_id = v_round.campaign_id
      and user_id = p_user_id and controller = 'human'
  ) then raise exception 'controlled character not found'; end if;

  insert into public.player_intents(round_id, member_id, character_id, action, ready)
  values (p_round_id, v_member.id, p_character_id, btrim(p_action), coalesce(p_ready, false))
  on conflict (round_id, member_id) do update
  set character_id = excluded.character_id, action = excluded.action,
      ready = excluded.ready, updated_at = now()
  returning id into v_intent_id;

  if coalesce(p_ready, false) and not exists (
    select 1
    from public.campaign_members member
    join public.characters character
      on character.campaign_id = member.campaign_id
     and character.user_id = member.user_id
     and character.controller = 'human'::public.character_controller
    where member.campaign_id = v_round.campaign_id
      and member.active
      and not exists (
        select 1 from public.player_intents intent
        where intent.round_id = p_round_id and intent.member_id = member.id and intent.ready
      )
  ) then
    v_job_id := public.enqueue_round_resolution(p_round_id, null);
  end if;
  return jsonb_build_object('intentId', v_intent_id, 'jobId', v_job_id);
end;
$$;

-- Repair campaigns created before starter parties were automatic.
insert into public.characters(
  campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
  defense, proficiency, inventory, statuses, persona
)
select
  campaign.id,
  campaign.owner_id,
  left(coalesce(nullif(btrim(profile.display_name), ''), 'Wayfinder'), 80),
  left('An adaptable protagonist ready to confront the opening mystery of ' || world.title || '.', 600),
  'human'::public.character_controller,
  '{"str":10,"dex":12,"con":12,"int":11,"wis":13,"cha":10}'::jsonb,
  12,
  12,
  12,
  2,
  '["Field kit","Personal keepsake"]'::jsonb,
  '[]'::jsonb,
  jsonb_build_object(
    'voice', 'Defined by the player.',
    'motivation', 'Discover what the opening scene is hiding.',
    'flaw', 'Still learning what this world demands.',
    'bond', 'Protect the party through the first danger.'
  )
from public.campaigns campaign
join public.worlds world on world.id = campaign.world_id
left join public.profiles profile on profile.id = campaign.owner_id
where campaign.status = 'active'
  and not exists (
    select 1 from public.characters character
    where character.campaign_id = campaign.id
      and character.user_id = campaign.owner_id
  );

insert into public.characters(
  campaign_id, user_id, name, concept, controller, abilities, hp, max_hp,
  defense, proficiency, inventory, statuses, persona
)
select
  campaign.id,
  null,
  'Echo',
  left('A persistent AI companion shaped by the ' || world.genre || ' world of ' || world.title || ', ready to support the player without taking over their choices.', 600),
  'ai'::public.character_controller,
  '{"str":9,"dex":13,"con":11,"int":13,"wis":12,"cha":10}'::jsonb,
  11,
  11,
  13,
  2,
  '["Survey kit","Emergency supplies"]'::jsonb,
  '[]'::jsonb,
  jsonb_build_object(
    'voice', 'Observant, concise, and quietly curious.',
    'motivation', 'Help the party understand this unfamiliar world.',
    'flaw', 'Sometimes values patterns more than instinct.',
    'bond', 'Stays beside the player when the path turns dangerous.'
  )
from public.campaigns campaign
join public.worlds world on world.id = campaign.world_id
where campaign.status = 'active'
  and not exists (
    select 1 from public.characters character
    where character.campaign_id = campaign.id
      and character.controller = 'ai'::public.character_controller
  );

create or replace function public.dng_schema_version()
returns integer language sql stable security definer set search_path = '' as $$
  select 10;
$$;

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

notify pgrst, 'reload schema';
