import 'server-only'

import { createAdminClient } from '@/lib/supabase/admin'
import { emailPattern } from '@/lib/like'
import { SITE_URL } from '@/lib/app-url'
import { sendPlatformEmail } from '@/lib/email/send'
import { loadUserContexts } from '@/lib/data/modes'
import { logAutoParticipantRuleEvent, type Actor } from '@/lib/data/auto-participant-events'
import {
  COMPANY_SCOPES, LIVE_RULE_STATUSES, canTransition, isUnclaimed, normalizeEmail, roleTypeLabel, scopeLabel,
} from '@/lib/domain/auto-participants'
import { ROLE_TYPES, type RoleType } from '@/lib/domain/modes'
import type { AutoParticipantRule, AutoParticipantRuleStatus, AutoParticipantScope } from '@/types/db'

/**
 * Auto-Participant rules — the I/O (2026-09-30). Rules in
 * src/lib/domain/auto-participants.ts; the resolver that applies them in
 * src/lib/data/auto-participants.ts.
 */

type Admin = ReturnType<typeof createAdminClient>
type Result<T = Record<string, never>> = ({ ok: true } & T) | { ok: false; error: string; status?: number }

const TOKEN_DAYS = 14
const randomToken = () => crypto.randomUUID().replace(/-/g, '') + crypto.randomUUID().replace(/-/g, '').slice(0, 16)
const expiry = () => new Date(Date.now() + TOKEN_DAYS * 86_400_000).toISOString()
const appOrigin = () => process.env.NEXT_PUBLIC_APP_URL?.trim().replace(/\/+$/, '') || SITE_URL
export const acceptLink = (token: string, origin = appOrigin()) => `${origin}/auto-participants/${token}`

/* --------------------------------------------------------------- views */

/** A rule as the settings page and the API show it. Never carries the accept token. */
export interface RuleView {
  id: string
  rule_type: AutoParticipantRule['rule_type']
  scope_type: AutoParticipantScope
  scope_label: string
  status: AutoParticipantRuleStatus
  suspended_reason: string | null
  requires_acceptance: boolean
  participant: { user_id: string | null; email: string; name: string; role_type: string | null; role_label: string }
  owner: { user_id: string | null; name: string | null; company_id: string | null; company_name: string | null }
  created_by: { user_id: string; name: string }
  effective_from: string | null
  accepted_at: string | null
  created_at: string
  updated_at: string
}

interface PersonRow { id: string; email: string; full_name: string | null; claim_status?: string | null }

async function people(admin: Admin, ids: string[]): Promise<Map<string, PersonRow>> {
  const unique = [...new Set(ids.filter(Boolean))]
  if (unique.length === 0) return new Map()
  const { data } = await admin.from('profiles').select('id, email, full_name, claim_status').in('id', unique)
  return new Map(((data ?? []) as PersonRow[]).map((p) => [p.id, p]))
}

async function companies(admin: Admin, ids: string[]): Promise<Map<string, string>> {
  const unique = [...new Set(ids.filter(Boolean))]
  if (unique.length === 0) return new Map()
  const { data } = await admin.from('companies').select('id, display_name, legal_name').in('id', unique)
  return new Map(((data ?? []) as { id: string; display_name: string; legal_name: string }[]).map((c) => [c.id, c.display_name || c.legal_name]))
}

export async function toViews(rules: AutoParticipantRule[], admin: Admin = createAdminClient()): Promise<RuleView[]> {
  const persons = await people(admin, rules.flatMap((r) => [r.owner_user_id ?? '', r.participant_user_id ?? '', r.created_by]))
  const names = await companies(admin, rules.map((r) => r.owner_company_id ?? ''))
  return rules.map((r) => {
    const p = r.participant_user_id ? persons.get(r.participant_user_id) : null
    const fallbackName = [r.participant_first_name, r.participant_last_name].filter(Boolean).join(' ')
    return {
      id: r.id,
      rule_type: r.rule_type,
      scope_type: r.scope_type,
      scope_label: scopeLabel(r.scope_type),
      status: r.status,
      suspended_reason: r.suspended_reason,
      requires_acceptance: r.requires_acceptance,
      participant: {
        user_id: r.participant_user_id,
        email: r.participant_email,
        name: p?.full_name || fallbackName || r.participant_email,
        role_type: r.participant_role_type,
        role_label: r.participant_role_type ? roleTypeLabel(r.participant_role_type) : 'Chosen per Trip',
      },
      owner: {
        user_id: r.owner_user_id,
        name: r.owner_user_id ? persons.get(r.owner_user_id)?.full_name || persons.get(r.owner_user_id)?.email || null : null,
        company_id: r.owner_company_id,
        company_name: r.owner_company_id ? names.get(r.owner_company_id) ?? null : null,
      },
      created_by: { user_id: r.created_by, name: persons.get(r.created_by)?.full_name || persons.get(r.created_by)?.email || 'HeavyHaul Agent' },
      effective_from: r.effective_from,
      accepted_at: r.accepted_at,
      created_at: r.created_at,
      updated_at: r.updated_at,
    }
  })
}

/* --------------------------------------------------------------- reads */

export async function loadRule(id: string, admin: Admin = createAdminClient()): Promise<AutoParticipantRule | null> {
  const { data } = await admin.from('auto_participant_rules').select('*').eq('id', id).maybeSingle()
  return (data as AutoParticipantRule | null) ?? null
}

export async function loadRuleByToken(token: string, admin: Admin = createAdminClient()): Promise<AutoParticipantRule | null> {
  if (!token || token.length < 16) return null
  const { data } = await admin.from('auto_participant_rules').select('*').eq('accept_token', token).maybeSingle()
  return (data as AutoParticipantRule | null) ?? null
}

/** Everything the settings page shows a person (Task 6.1). */
export async function listRulesForUser(user: { id: string; email: string }, admin: Admin = createAdminClient()): Promise<{ mine: RuleView[]; pending_for_me: RuleView[]; including_me: RuleView[] }> {
  const email = normalizeEmail(user.email)
  const [mine, byUser, byEmail] = await Promise.all([
    admin.from('auto_participant_rules').select('*').eq('rule_type', 'personal').eq('owner_user_id', user.id).neq('status', 'revoked').order('created_at'),
    admin.from('auto_participant_rules').select('*').eq('participant_user_id', user.id).order('created_at'),
    admin.from('auto_participant_rules').select('*').eq('participant_email_normalized', email).order('created_at'),
  ])
  if (mine.error) return { mine: [], pending_for_me: [], including_me: [] }
  const named = new Map<string, AutoParticipantRule>()
  for (const r of [...((byUser.data ?? []) as AutoParticipantRule[]), ...((byEmail.data ?? []) as AutoParticipantRule[])]) named.set(r.id, r)
  const forMe = [...named.values()]
  const [mineV, pendingV, includingV] = await Promise.all([
    toViews((mine.data ?? []) as AutoParticipantRule[], admin),
    toViews(forMe.filter((r) => r.status === 'pending'), admin),
    toViews(forMe.filter((r) => r.status === 'active' || r.status === 'disabled' || r.status === 'suspended'), admin),
  ])
  return { mine: mineV, pending_for_me: pendingV, including_me: includingV }
}

/** Company rules plus the member list for the picker (Task 7.2). */
export async function listCompanyRules(companyId: string, admin: Admin = createAdminClient()): Promise<{ rules: RuleView[]; members: Array<{ user_id: string; name: string; email: string; roles: string[]; role_labels: string[] }> }> {
  const [{ data: rules }, { data: memberships }] = await Promise.all([
    admin.from('auto_participant_rules').select('*').eq('rule_type', 'company').eq('owner_company_id', companyId).neq('status', 'revoked').order('created_at'),
    admin.from('company_memberships').select('user_id, status').eq('company_id', companyId).eq('status', 'approved'),
  ])
  const memberIds = ((memberships ?? []) as { user_id: string }[]).map((m) => m.user_id)
  const persons = await people(admin, memberIds)
  const { data: roles } = memberIds.length
    ? await admin.from('membership_roles').select('user_id, role_type, status').eq('company_id', companyId).eq('status', 'active').in('user_id', memberIds)
    : { data: [] }
  const rolesBy = new Map<string, string[]>()
  for (const r of (roles ?? []) as { user_id: string; role_type: string }[]) rolesBy.set(r.user_id, [...(rolesBy.get(r.user_id) ?? []), r.role_type])
  const members = memberIds
    .map((id) => {
      const p = persons.get(id)
      const rs = rolesBy.get(id) ?? []
      return { user_id: id, name: p?.full_name || p?.email || id, email: p?.email ?? '', roles: rs, role_labels: rs.map(roleTypeLabel) }
    })
    .sort((a, b) => a.name.localeCompare(b.name))
  return { rules: await toViews((rules ?? []) as AutoParticipantRule[], admin), members }
}

/* ------------------------------------------------------- resolution */

async function profileForEmail(email: string, admin: Admin): Promise<PersonRow | null> {
  const { data } = await admin.from('profiles').select('id, email, full_name, claim_status').ilike('email', emailPattern(normalizeEmail(email))).maybeSingle()
  return (data as PersonRow | null) ?? null
}

/** D5 / §31: only a real account (not an unclaimed historical profile) resolves now. */
function realAccount(p: PersonRow | null): PersonRow | null {
  return p && !isUnclaimed(p.claim_status) ? p : null
}

async function activeRoleTypes(userId: string, admin: Admin): Promise<RoleType[]> {
  const { data } = await admin.from('profiles').select('default_role').eq('id', userId).maybeSingle()
  const contexts = await loadUserContexts({ id: userId, role: ((data as { default_role?: string } | null)?.default_role ?? 'dispatcher') as string }, admin)
  return [...new Set(contexts.filter((c) => c.status === 'active').map((c) => c.roleType))]
}

async function rolesAtCompany(userId: string, companyId: string, admin: Admin): Promise<string[]> {
  const { data } = await admin.from('membership_roles').select('role_type').eq('user_id', userId).eq('company_id', companyId).eq('status', 'active')
  return ((data ?? []) as { role_type: string }[]).map((r) => r.role_type)
}

/* -------------------------------------------------------- personal */

export async function createPersonalRule(params: {
  owner: { id: string; email: string; name: string }
  email: string
  first_name?: string | null
  last_name?: string | null
  origin?: string
  admin?: Admin
}): Promise<Result<{ rule: AutoParticipantRule }>> {
  const admin = params.admin ?? createAdminClient()
  const email = normalizeEmail(params.email)
  if (!email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return { ok: false, error: 'Enter a valid email address.', status: 400 }
  if (email === normalizeEmail(params.owner.email)) return { ok: false, error: 'You cannot add yourself.', status: 400 }
  const person = realAccount(await profileForEmail(email, admin))
  const first = (params.first_name ?? '').trim(), last = (params.last_name ?? '').trim()
  if (!person && (!first || !last)) return { ok: false, error: 'This email has no HeavyHaul Agent account yet. Enter their first and last name so they can be invited.', status: 422 }

  const row = {
    rule_type: 'personal',
    owner_user_id: params.owner.id,
    owner_company_id: null,
    participant_user_id: person?.id ?? null,
    participant_email: person?.email ?? params.email.trim(),
    participant_email_normalized: email,
    participant_first_name: person ? null : first,
    participant_last_name: person ? null : last,
    participant_role_type: null,
    scope_type: 'all_personal_trips',
    status: 'pending',
    requires_acceptance: true,
    accept_token: randomToken(),
    token_expires_at: expiry(),
    created_by: params.owner.id,
  }
  const { data, error } = await admin.from('auto_participant_rules').insert(row).select('*').single()
  if (error) {
    if (error.code === '23505') return { ok: false, error: 'You already have a request or rule for this person.', status: 409 }
    return { ok: false, error: 'The request could not be saved.', status: 500 }
  }
  const rule = data as AutoParticipantRule
  await logAutoParticipantRuleEvent({ ruleId: rule.id, event: 'rule_requested', actor: { id: params.owner.id, label: params.owner.name || params.owner.email }, detail: { participant_email: email, has_account: !!person } })
  await sendRequestEmail(rule, { ownerName: params.owner.name || params.owner.email, origin: params.origin, admin })
  return { ok: true, rule }
}

async function sendRequestEmail(rule: AutoParticipantRule, opts: { ownerName?: string; companyName?: string; requestedBy?: string; origin?: string; admin: Admin }) {
  const person = rule.participant_user_id ? (await people(opts.admin, [rule.participant_user_id])).get(rule.participant_user_id) : null
  const firstName = (person?.full_name || rule.participant_first_name || '').trim().split(/\s+/)[0] || rule.participant_email
  const link = acceptLink(rule.accept_token ?? '', opts.origin)
  try {
    if (rule.rule_type === 'company') {
      await sendPlatformEmail({
        templateKey: 'auto_participant_company_request', to: rule.participant_email, userId: rule.participant_user_id, origin: opts.origin,
        data: { first_name: firstName, company_name: opts.companyName ?? '', scope_label: scopeLabel(rule.scope_type), requested_by: opts.requestedBy ?? '', accept_link: link },
      })
    } else {
      await sendPlatformEmail({
        templateKey: 'auto_participant_request', to: rule.participant_email, userId: rule.participant_user_id, origin: opts.origin,
        data: { first_name: firstName, owner_name: opts.ownerName ?? '', accept_link: link },
      })
    }
  } catch (error) {
    console.error('auto-participant request email failed', error instanceof Error ? error.message : error)
  }
}

/* --------------------------------------------------------- company */

export async function createCompanyRule(params: {
  actor: { id: string; email: string; name: string }
  companyId: string
  companyName: string
  member_user_id?: string | null
  email?: string | null
  first_name?: string | null
  last_name?: string | null
  scope_type: string
  confirm_external?: boolean
  origin?: string
  admin?: Admin
}): Promise<Result<{ rule: AutoParticipantRule; external: boolean }>> {
  const admin = params.admin ?? createAdminClient()
  if (!(COMPANY_SCOPES as string[]).includes(params.scope_type)) return { ok: false, error: 'Choose a scope.', status: 400 }
  const scope = params.scope_type as AutoParticipantScope

  // Who?
  let person: PersonRow | null = null
  if (params.member_user_id) {
    person = (await people(admin, [params.member_user_id])).get(params.member_user_id) ?? null
    if (!person) return { ok: false, error: 'Member not found.', status: 404 }
  } else if (params.email) {
    person = realAccount(await profileForEmail(params.email, admin))
  } else return { ok: false, error: 'Pick a member or enter an email.', status: 400 }
  const email = normalizeEmail(person?.email ?? params.email ?? '')
  if (!email) return { ok: false, error: 'Enter a valid email address.', status: 400 }

  // Member of this company?
  const { data: m } = person ? await admin.from('company_memberships').select('id, status').eq('user_id', person.id).eq('company_id', params.companyId).maybeSingle() : { data: null }
  const isMember = (m as { status?: string } | null)?.status === 'approved'

  const first = (params.first_name ?? '').trim(), last = (params.last_name ?? '').trim()
  if (!person && (!first || !last)) return { ok: false, error: 'This email has no HeavyHaul Agent account yet. Enter their first and last name so they can be invited.', status: 422 }
  if (!isMember && !params.confirm_external) return { ok: false, error: 'Confirm that this person is outside the company and will gain access to future company Trips.', status: 412 }

  let roleType: string | null = null
  if (isMember && person) {
    const roles = await rolesAtCompany(person.id, params.companyId, admin)
    if (roles.length === 0) return { ok: false, error: `This person must hold a role at ${params.companyName} first.`, status: 409 }
    roleType = roles.length === 1 ? roles[0] : null // A4: several roles → the member picks per trip
  }

  const now = new Date().toISOString()
  const row = {
    rule_type: 'company',
    owner_user_id: null,
    owner_company_id: params.companyId,
    participant_user_id: person?.id ?? null,
    participant_email: person?.email ?? params.email!.trim(),
    participant_email_normalized: email,
    participant_first_name: person ? null : first,
    participant_last_name: person ? null : last,
    participant_role_type: roleType,
    scope_type: scope,
    status: isMember ? 'active' : 'pending',
    requires_acceptance: !isMember,
    accepted_at: isMember ? now : null,
    accepted_by: isMember ? params.actor.id : null,
    effective_from: isMember ? now : null,
    accept_token: randomToken(),
    token_expires_at: expiry(),
    created_by: params.actor.id,
  }
  const { data, error } = await admin.from('auto_participant_rules').insert(row).select('*').single()
  if (error) {
    if (error.code === '23505') return { ok: false, error: 'This person already has a rule with that scope for this company.', status: 409 }
    return { ok: false, error: 'The rule could not be saved.', status: 500 }
  }
  const rule = data as AutoParticipantRule
  const actor: Actor = { id: params.actor.id, label: params.actor.name || params.actor.email }
  await logAutoParticipantRuleEvent({ ruleId: rule.id, event: isMember ? 'rule_created' : 'rule_requested', actor, detail: { company_id: params.companyId, scope, participant_email: email, external: !isMember, authorized_by: actor.label } })
  if (!isMember) await sendRequestEmail(rule, { companyName: params.companyName, requestedBy: actor.label, origin: params.origin, admin })
  return { ok: true, rule, external: !isMember }
}

/* ------------------------------------------------------- responses */

/**
 * Accept / decline a pending request, or stop being included (D16). The
 * signed-in person must BE the person the rule names — the email check is
 * mandatory here, unlike the trip-invite accept.
 */
export async function respondToRule(params: {
  user: { id: string; email: string; name: string; role: string }
  ruleId?: string
  token?: string
  decision: 'accept' | 'decline' | 'stop'
  role_type?: string | null
  admin?: Admin
}): Promise<Result<{ rule: AutoParticipantRule; needs_role?: string[] }>> {
  const admin = params.admin ?? createAdminClient()
  const rule = params.token ? await loadRuleByToken(params.token, admin) : params.ruleId ? await loadRule(params.ruleId, admin) : null
  if (!rule) return { ok: false, error: 'This request no longer exists.', status: 404 }
  const mine = rule.participant_user_id === params.user.id || rule.participant_email_normalized === normalizeEmail(params.user.email)
  if (!mine) return { ok: false, error: 'This request was sent to a different email address.', status: 403 }
  const actor: Actor = { id: params.user.id, label: params.user.name || params.user.email }

  if (params.decision === 'stop') {
    if (!canTransition(rule.status, 'revoked') || rule.status === 'pending') return { ok: false, error: 'This rule is not active.', status: 409 }
    const { error } = await admin.from('auto_participant_rules').update({ status: 'revoked', disabled_at: new Date().toISOString(), disabled_by: params.user.id }).eq('id', rule.id)
    if (error) return { ok: false, error: 'Could not update the rule.', status: 500 }
    await logAutoParticipantRuleEvent({ ruleId: rule.id, event: 'rule_stopped_by_participant', actor })
    return { ok: true, rule: { ...rule, status: 'revoked' } }
  }

  if (rule.status !== 'pending') return { ok: false, error: `This request is ${rule.status}.`, status: 409 }
  if (rule.token_expires_at && rule.token_expires_at < new Date().toISOString()) return { ok: false, error: 'This request has expired. Ask for a new one.', status: 410 }

  if (params.decision === 'decline') {
    const { error } = await admin.from('auto_participant_rules').update({ status: 'declined', participant_user_id: rule.participant_user_id ?? params.user.id }).eq('id', rule.id)
    if (error) return { ok: false, error: 'Could not update the rule.', status: 500 }
    await logAutoParticipantRuleEvent({ ruleId: rule.id, event: 'rule_declined', actor })
    return { ok: true, rule: { ...rule, status: 'declined' } }
  }

  // Accept: the person keeps their own role (D1); with several, they choose (D6).
  const roles = await activeRoleTypes(params.user.id, admin)
  if (roles.length === 0) return { ok: false, error: 'Choose your role first (Settings → Roles & Workspaces), then accept.', status: 409 }
  let roleType: string | null = null
  if (roles.length === 1) roleType = roles[0]
  else if (params.role_type && (roles as string[]).includes(params.role_type)) roleType = params.role_type
  else return { ok: false, error: 'Choose which of your roles applies to this rule.', status: 400, ...( { needs_role: roles } as object) } as Result<{ rule: AutoParticipantRule; needs_role?: string[] }>
  const now = new Date().toISOString()
  const { data, error } = await admin.from('auto_participant_rules')
    .update({ status: 'active', participant_user_id: params.user.id, participant_role_type: roleType, accepted_at: now, accepted_by: params.user.id, effective_from: now })
    .eq('id', rule.id).select('*').single()
  if (error) return { ok: false, error: 'Could not update the rule.', status: 500 }
  await logAutoParticipantRuleEvent({ ruleId: rule.id, event: 'rule_accepted', actor, detail: { role_type: roleType } })
  return { ok: true, rule: data as AutoParticipantRule }
}

/** Roles a person may pick when accepting (for the UI). */
export async function acceptableRoles(userId: string, admin: Admin = createAdminClient()): Promise<Array<{ role_type: string; label: string }>> {
  return (await activeRoleTypes(userId, admin)).map((r) => ({ role_type: r, label: roleTypeLabel(r) }))
}

/* ------------------------------------------------------------- ops */

export type RuleOp = 'disable' | 'reactivate' | 'remove' | 'cancel' | 'resend' | 'change_scope'

/** Owner (personal) or Company Admin (company) manages a rule (§43). */
export async function ruleOperation(params: {
  actor: { id: string; email: string; name: string }
  rule: AutoParticipantRule
  op: RuleOp
  scope_type?: string | null
  ownerName?: string
  companyName?: string
  origin?: string
  admin?: Admin
}): Promise<Result<{ rule: AutoParticipantRule }>> {
  const admin = params.admin ?? createAdminClient()
  const { rule, op } = params
  const actor: Actor = { id: params.actor.id, label: params.actor.name || params.actor.email }
  const now = new Date().toISOString()
  const apply = async (patch: Partial<AutoParticipantRule>, event: Parameters<typeof logAutoParticipantRuleEvent>[0]['event'], detail?: Record<string, unknown>): Promise<Result<{ rule: AutoParticipantRule }>> => {
    const { data, error } = await admin.from('auto_participant_rules').update(patch).eq('id', rule.id).select('*').single()
    if (error) return { ok: false, error: error.code === '23505' ? 'A rule with that scope already exists for this person.' : 'Could not update the rule.', status: error.code === '23505' ? 409 : 500 }
    await logAutoParticipantRuleEvent({ ruleId: rule.id, event: event, actor, detail })
    return { ok: true, rule: data as AutoParticipantRule }
  }
  switch (op) {
    case 'disable':
      if (!canTransition(rule.status, 'disabled')) return { ok: false, error: 'Only an active rule can be disabled.', status: 409 }
      return apply({ status: 'disabled', disabled_at: now, disabled_by: params.actor.id }, 'rule_disabled')
    case 'reactivate': {
      if (!canTransition(rule.status, 'active') || rule.status === 'pending') return { ok: false, error: 'This rule cannot be reactivated.', status: 409 }
      if (rule.status === 'suspended' && rule.rule_type === 'company' && rule.participant_user_id && rule.owner_company_id && !rule.requires_acceptance) {
        const { data: m } = await admin.from('company_memberships').select('status').eq('user_id', rule.participant_user_id).eq('company_id', rule.owner_company_id).maybeSingle()
        if ((m as { status?: string } | null)?.status !== 'approved') return { ok: false, error: 'This person is no longer a member of the company. Approve their membership first.', status: 409 }
      }
      return apply({ status: 'active', disabled_at: null, disabled_by: null, suspended_reason: null, effective_from: rule.effective_from ?? now }, 'rule_reactivated')
    }
    case 'remove':
    case 'cancel':
      if (!canTransition(rule.status, 'revoked')) return { ok: false, error: 'This rule is already removed.', status: 409 }
      return apply({ status: 'revoked', disabled_at: now, disabled_by: params.actor.id }, op === 'cancel' ? 'rule_cancelled' : 'rule_removed')
    case 'resend': {
      if (rule.status !== 'pending') return { ok: false, error: 'Only a pending request can be resent.', status: 409 }
      const res = await apply({ accept_token: randomToken(), token_expires_at: expiry() }, 'rule_resent')
      if (res.ok) await sendRequestEmail(res.rule, { ownerName: params.ownerName, companyName: params.companyName, requestedBy: actor.label, origin: params.origin, admin })
      return res
    }
    case 'change_scope': {
      const allowed = rule.rule_type === 'company' ? (COMPANY_SCOPES as string[]) : ['all_personal_trips']
      if (!params.scope_type || !allowed.includes(params.scope_type)) return { ok: false, error: 'Choose a valid scope.', status: 400 }
      if (!(LIVE_RULE_STATUSES as string[]).includes(rule.status)) return { ok: false, error: 'This rule is no longer live.', status: 409 }
      return apply({ scope_type: params.scope_type as AutoParticipantScope }, 'rule_scope_changed', { from: rule.scope_type, to: params.scope_type })
    }
    default:
      return { ok: false, error: 'Unknown operation.', status: 400 }
  }
}

/* ----------------------------------------------------- memberships */

/**
 * §36: a person who leaves the company stops being added to its trips. The
 * rules are suspended (not removed), existing trips untouched, reason logged.
 */
export async function suspendCompanyRulesForMember(params: { userId: string; companyId: string; reason: string; actor: Actor; admin?: Admin }): Promise<number> {
  const admin = params.admin ?? createAdminClient()
  const { data, error } = await admin.from('auto_participant_rules')
    .update({ status: 'suspended', suspended_reason: params.reason })
    .eq('rule_type', 'company').eq('owner_company_id', params.companyId).eq('participant_user_id', params.userId).eq('status', 'active').eq('requires_acceptance', false)
    .select('id')
  if (error) {
    if (!/does not exist|schema cache/i.test(error.message)) console.error('auto-participant suspension failed', error.message)
    return 0
  }
  const rows = (data ?? []) as { id: string }[]
  for (const r of rows) await logAutoParticipantRuleEvent({ ruleId: r.id, event: 'rule_suspended', actor: params.actor, detail: { reason: params.reason } })
  return rows.length
}

export function isRoleType(value: string | null | undefined): value is RoleType {
  return !!value && (ROLE_TYPES as string[]).includes(value)
}
