"use client"

import { useSearchParams } from "next/navigation"
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"
import {
  API_TO_FORMAT,
  FORMAT_TO_API,
  ORDINARY_FORMATS,
  SCRAMBLE_VS_SOLO,
  SIDE_IS_UNIT_FORMAT,
  TWO_SIDED,
  apiStatLevel,
  showsTeamStep,
} from "@/lib/ny-runde/formats"
import { apiGenderOf } from "@/lib/ny-runde/api"
import type { ApiGender, Course, CourseMeta, FormatId, Player, StatLevel, Sub1, Visibility } from "@/lib/ny-runde/types"

/** Samme faste sett/rekkefølge som friends.py sin Category. */
export const CATEGORY_OPTIONS = [
  { code: "spouse", label: "Make" },
  { code: "close_family", label: "Nær familie" },
  { code: "extended_family", label: "Storfamilie" },
  { code: "close_friends", label: "Nære venner" },
  { code: "golf_friends", label: "Golfvenner" },
  { code: "colleagues", label: "Kollegaer" },
  { code: "business", label: "Forretningsforbindelser" },
  { code: "classmates", label: "Studiekamerater" },
  { code: "acquaintances", label: "Perifere bekjente" },
  { code: "other", label: "Ymse" },
] as const

export interface WizardState {
  step: number // 1..5 (logisk; steg 4 kan hoppes over)

  // Auth/profil
  loadingMe: boolean
  ownGender: ApiGender | null

  // Steg 1 under-veiviser
  s1Sub: Sub1
  ownCreateOrigin: Sub1
  templateMode: boolean
  courseSource: "official" | "own"
  selectedFacilitySlug?: string
  course?: Course
  courseMeta?: CourseMeta
  templateSeed?: Course

  // Steg 1-felt
  roundName: string
  teeId?: string
  date: string
  teeTime: string
  startHole: number

  // Steg 2
  statLevel: StatLevel
  format: FormatId
  numHoles: 9 | 18
  matchExcludeHcp: boolean
  skinsScoring: "net" | "gross"
  skinsTie: "carry" | "split"
  bbbSweep: boolean
  shambleBestN: number
  soloAllowance: number
  useHandicap: boolean
  useCourseHcpAdj: boolean
  useMatchplayHcp: boolean
  hcpPercent: string

  // Steg 3
  players: Player[]

  // Steg 4
  sideALabel: string
  sideBLabel: string
  sideAssign: Record<string, "A" | "B" | undefined>
  moneyballSlots: Record<string, number>
  scrambleAssign: Record<string, "team" | "opponent" | undefined>

  // Steg 5
  visibility: Visibility
  categories: string[]

  submitting: boolean
  submitError: string | null

  // "Legg til en flight til" (migrasjon 035)
  flightGroupId: string | null
}

function todayISO() {
  const now = new Date()
  const offset = now.getTimezoneOffset()
  const local = new Date(now.getTime() - offset * 60 * 1000)
  return local.toISOString().slice(0, 10)
}
function nowTime() {
  const d = new Date()
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`
}

function initialState(params: {
  flightGroupId: string | null
  prefillPlayedAt?: string
  prefillStartHole?: string | null
  prefillHoles?: string | null
  prefillPlayFormatApi?: string | null
}): WizardState {
  const statLevel: StatLevel = "score"
  const format = (params.prefillPlayFormatApi && API_TO_FORMAT[params.prefillPlayFormatApi]) || "slagspill"
  return {
    step: 1,
    loadingMe: true,
    ownGender: null,
    s1Sub: "source",
    ownCreateOrigin: "own-search",
    templateMode: false,
    courseSource: "official",
    roundName: "",
    date: params.prefillPlayedAt ?? todayISO(),
    teeTime: nowTime(),
    startHole: params.prefillStartHole ? Number(params.prefillStartHole) : 1,
    statLevel,
    format,
    numHoles: params.prefillHoles === "9" ? 9 : 18,
    matchExcludeHcp: true,
    skinsScoring: "net",
    skinsTie: "carry",
    bbbSweep: false,
    shambleBestN: 2,
    soloAllowance: 100,
    useHandicap: true,
    useCourseHcpAdj: true,
    useMatchplayHcp: true,
    hcpPercent: "",
    players: [],
    sideALabel: "",
    sideBLabel: "",
    sideAssign: {},
    moneyballSlots: {},
    scrambleAssign: {},
    visibility: "private",
    categories: CATEGORY_OPTIONS.map((c) => c.code),
    submitting: false,
    submitError: null,
    flightGroupId: params.flightGroupId,
  }
}

interface WizardCtx {
  state: WizardState
  patch: (p: Partial<WizardState>) => void
  visibleSteps: number[]
  goNext: () => void
  goBack: () => void
  canProceedStep1: boolean
  isAdditionalFlight: boolean
  submit: () => Promise<void>
  exit: () => void
}

const Ctx = createContext<WizardCtx | null>(null)

export function WizardProvider({
  children,
  onExit,
  onCreated,
}: {
  children: React.ReactNode
  onExit: () => void
  onCreated: (roundId: string) => void
}) {
  const searchParams = useSearchParams()
  const flightGroupId = searchParams.get("flightGroupId")
  const prefillPlayedAt = searchParams.get("playedAt") ?? undefined
  const prefillStartHole = searchParams.get("startHole")
  const prefillHoles = searchParams.get("holes")
  const prefillPlayFormatApi = searchParams.get("playFormat")
  const isAdditionalFlight = Boolean(flightGroupId)

  const [state, setState] = useState<WizardState>(() =>
    initialState({ flightGroupId, prefillPlayedAt, prefillStartHole, prefillHoles, prefillPlayFormatApi }),
  )

  const patch = useCallback((p: Partial<WizardState>) => {
    setState((s) => ({ ...s, ...p }))
  }, [])

  // Egen profil -- eieren av runden er ALLTID første spiller i listen.
  useEffect(() => {
    let cancelled = false
    async function loadMe() {
      try {
        const res = await fetch("/auth/me", { credentials: "include" })
        if (res.status === 401) {
          onExit()
          return
        }
        const data: {
          gender: ApiGender | "x" | null
          handicap_index: number | null
          first_name: string | null
          last_name: string | null
        } = await res.json()
        if (cancelled) return
        const ownGender = data.gender === "m" || data.gender === "f" ? data.gender : null
        const ownerPlayer: Player = {
          key: "owner",
          kind: "owner",
          firstName: data.first_name ?? "",
          lastName: data.last_name ?? "",
          gender: data.gender === "f" ? "kvinne" : "mann",
          isGuest: false,
          hcp: data.handicap_index,
          hcpFromProfile: true,
          statLevel: "score",
        }
        setState((s) => ({ ...s, ownGender, loadingMe: false, players: [ownerPlayer] }))
      } catch {
        if (!cancelled) setState((s) => ({ ...s, loadingMe: false }))
      }
    }
    void loadMe()
    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // Eierens EGET teeId/statLevel-felt i players[] (lest av steg 3 sitt
  // spillerkort, OG -- for statLevel -- av submit() sin faktiske innsending,
  // se linje ~317) er egne, atskilte felt fra state.teeId/state.statLevel
  // (steg 1/steg 2 sine felt). De ble tidligere kun synkronisert ÉN gang,
  // idet banen først velges (pickCourse i step1-course-time.tsx) -- endrer
  // brukeren utslag i "Bane & tid"-bekreftelsen ETTERPÅ, eller statistikknivå
  // i "Spilleform", flyter de to fra hverandre. For utslag var dette rent
  // kosmetisk (steg 3 viste et gammelt utslag, men submit() leser
  // state.teeId direkte for eieren) -- for statistikknivå var det en REELL
  // datafeil: "Statistikk for deg selv"-valget i steg 2 hadde INGEN effekt
  // på selve innsendingen for eieren (kun på default for NYE medspillere via
  // defaultStat-proppen), siden owner.statLevel forble hardkodet "score" fra
  // loadMe() over. Bruker-rapportert regresjon 2026-08-15 (video vedlagt) --
  // holder nå de to i kontinuerlig synk fremfor kun ved første valg.
  useEffect(() => {
    setState((s) => {
      const owner = s.players.find((p) => p.kind === "owner")
      if (!owner || (owner.teeId === s.teeId && owner.statLevel === s.statLevel)) return s
      return {
        ...s,
        players: s.players.map((p) => (p.kind === "owner" ? { ...p, teeId: s.teeId, statLevel: s.statLevel } : p)),
      }
    })
  }, [state.teeId, state.statLevel])

  const hasTeamStep = showsTeamStep(state.format)
  const visibleSteps = useMemo(() => (hasTeamStep ? [1, 2, 3, 4, 5] : [1, 2, 3, 5]), [hasTeamStep])

  const canProceedStep1 = state.s1Sub === "fields" && !!state.course && !!state.teeId && !!state.date

  const goNext = useCallback(() => {
    setState((s) => {
      const team = showsTeamStep(s.format)
      const seq = team ? [1, 2, 3, 4, 5] : [1, 2, 3, 5]
      const idx = seq.indexOf(s.step)
      const next = seq[Math.min(idx + 1, seq.length - 1)]
      return { ...s, step: next }
    })
  }, [])

  // Speiler EKSAKT den forrige handleBack() sin steg-1-undertilstandslogikk
  // (new-round.tsx) -- hver s1Sub har sitt eget, spesifikke tilbake-mål.
  const goBack = useCallback(() => {
    setState((s) => {
      if (s.step === 1) {
        switch (s.s1Sub) {
          case "source":
            onExit()
            return s
          case "template-source":
            return { ...s, s1Sub: "own-create" }
          case "official-search":
            return { ...s, s1Sub: s.templateMode ? "template-source" : "source" }
          case "official-courses":
            return { ...s, s1Sub: "official-search" }
          case "own-search":
            return { ...s, s1Sub: s.templateMode ? "template-source" : "source" }
          case "international-search":
            return { ...s, s1Sub: "own-search" }
          case "own-create":
            return { ...s, s1Sub: s.ownCreateOrigin }
          case "fields":
            return { ...s, s1Sub: s.courseSource === "official" ? "official-courses" : "own-search" }
          default:
            return s
        }
      }
      const team = showsTeamStep(s.format)
      const seq = team ? [1, 2, 3, 4, 5] : [1, 2, 3, 5]
      const idx = seq.indexOf(s.step)
      const prev = seq[Math.max(idx - 1, 0)]
      return { ...s, step: prev }
    })
  }, [onExit])

  // --- Innsending -- speiler EKSAKT new-round.tsx sin submitWizard() -----
  const submit = useCallback(async () => {
    const s = state
    if (!s.course || !s.courseMeta || !s.teeId) return
    patch({ submitting: true, submitError: null })
    try {
      const startedAt = s.teeTime ? new Date(`${s.date}T${s.teeTime}`).toISOString() : null
      const apiFormat = FORMAT_TO_API[s.format]

      const percentValue = s.hcpPercent.trim() === "" ? undefined : Number(s.hcpPercent.replace(",", "."))
      const hasPercent = percentValue !== undefined && !Number.isNaN(percentValue)
      const twoSided = TWO_SIDED.includes(s.format)
      const matchplayIsDefault = !twoSided || s.useMatchplayHcp
      let allowanceOverride: Record<string, unknown> | null = null
      if (!(s.useHandicap && s.useCourseHcpAdj && matchplayIsDefault && !hasPercent)) {
        const override: Record<string, unknown> = { use_handicap: s.useHandicap, use_course_handicap: s.useCourseHcpAdj }
        if (twoSided) override.use_matchplay_handicap = s.useMatchplayHcp
        if (hasPercent) {
          const fraction = percentValue! / 100
          override.strategy = SIDE_IS_UNIT_FORMAT.includes(s.format)
            ? { type: "combined", percentage: fraction }
            : { type: "per_player", percentage: fraction }
        }
        allowanceOverride = override
      }

      const teeName = s.course.tees.find((t) => t.id === s.teeId)?.name ?? ""
      const owner = s.players.find((p) => p.kind === "owner")

      const shared = {
        tee_name: teeName,
        name: s.roundName.trim() || null,
        played_at: s.date,
        started_at: startedAt,
        start_hole: s.startHole,
        holes_planned: s.numHoles,
        stat_level: apiStatLevel(owner?.statLevel ?? "score"),
        play_format: apiFormat,
        exclude_owner_from_handicap: s.format === "match" ? s.matchExcludeHcp : false,
        skins_scoring: s.format === "skins" ? s.skinsScoring : null,
        skins_tie_handling: s.format === "skins" ? s.skinsTie : null,
        bbb_sweep_bonus_enabled: s.format === "bbb" ? s.bbbSweep : false,
        shamble_best_n: s.format === "shamble" ? s.shambleBestN : null,
        scramble_solo_individual_pct: SCRAMBLE_VS_SOLO.includes(s.format) ? s.soloAllowance : 100,
        visibility_mode: s.visibility === "private" ? "private" : s.visibility === "friends" ? "friends" : "public",
        visible_categories: s.visibility === "friends" ? s.categories : [],
        flight_group_id: s.flightGroupId || undefined,
        allowance_override: allowanceOverride,
      }
      const body =
        s.courseMeta.source === "teeoff"
          ? {
              course_source: "teeoff",
              teeoff_facility_slug: s.courseMeta.facilitySlug,
              teeoff_course_id: s.courseMeta.teeoffCourseId,
              ...shared,
            }
          : { course_source: "custom", personal_course_id: s.courseMeta.personalCourseId, ...shared }

      const roundRes = await fetch("/rounds", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify(body),
      })
      if (!roundRes.ok) throw new Error("create round:" + roundRes.status)
      const round: { id: string; participants: { id: string; is_owner: boolean }[] } = await roundRes.json()
      const roundId = round.id
      const ownerParticipantId = round.participants.find((p) => p.is_owner)?.id ?? round.participants[0]?.id

      const sideIdMap = new Map<string, string>()
      if (TWO_SIDED.includes(s.format)) {
        const labels: [string, string][] = [
          ["A", s.sideALabel],
          ["B", s.sideBLabel],
        ]
        for (const [localId, label] of labels) {
          const res = await fetch(`/rounds/${roundId}/sides`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            credentials: "include",
            body: JSON.stringify({ label: label || null }),
          })
          if (!res.ok) throw new Error("create side:" + res.status)
          const created: { id: string } = await res.json()
          sideIdMap.set(localId, created.id)
        }
      } else if (SCRAMBLE_VS_SOLO.includes(s.format)) {
        const roles: { localId: "team" | "opponent"; label: string; side_role: "team" | "individual" }[] = [
          { localId: "team", label: "Laget", side_role: "team" },
          { localId: "opponent", label: "Individuell motstander", side_role: "individual" },
        ]
        for (const { localId, label, side_role } of roles) {
          const res = await fetch(`/rounds/${roundId}/sides`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            credentials: "include",
            body: JSON.stringify({ label, side_role }),
          })
          if (!res.ok) throw new Error("create side:" + res.status)
          const created: { id: string } = await res.json()
          sideIdMap.set(localId, created.id)
        }
      }

      const sideKeyOf = (p: Player): string | undefined =>
        TWO_SIDED.includes(s.format) ? s.sideAssign[p.key] : SCRAMBLE_VS_SOLO.includes(s.format) ? s.scrambleAssign[p.key] : undefined

      const participantIdMap = new Map<string, string>([["owner", ownerParticipantId]])
      for (const p of s.players) {
        if (p.kind === "owner") continue
        const teeSelected = p.teeId ? s.course.tees.find((t) => t.id === p.teeId)?.name : undefined
        const sideKey = sideKeyOf(p)
        const realSideId = sideKey ? sideIdMap.get(sideKey) : undefined
        const participantBody: Record<string, unknown> = {
          stat_level: apiStatLevel(p.statLevel),
          ...(teeSelected ? { tee_name: teeSelected } : {}),
          ...(realSideId ? { round_side_id: realSideId } : {}),
        }
        if (p.accountId) {
          participantBody.user_id = p.accountId
        } else {
          participantBody.guest_first_name = p.firstName.trim()
          participantBody.guest_last_name = p.lastName.trim() || null
          participantBody.gender = apiGenderOf(p.gender ?? "mann")
          participantBody.handicap_index = p.hcp ?? null
          if (p.email && p.email.trim()) participantBody.guest_email = p.email.trim()
        }
        const res = await fetch(`/rounds/${roundId}/participants`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          credentials: "include",
          body: JSON.stringify(participantBody),
        })
        if (!res.ok) {
          const errBody = await res.json().catch(() => null)
          throw new Error(errBody?.detail?.message ?? `add participant: ${res.status}`)
        }
        const created: { id: string } = await res.json()
        participantIdMap.set(p.key, created.id)
      }

      if (TWO_SIDED.includes(s.format) || SCRAMBLE_VS_SOLO.includes(s.format)) {
        const sideKey = owner ? sideKeyOf(owner) : undefined
        const realSideId = sideKey ? sideIdMap.get(sideKey) : undefined
        if (realSideId) {
          await fetch(`/rounds/${roundId}/participants/${ownerParticipantId}`, {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            credentials: "include",
            body: JSON.stringify({ round_side_id: realSideId }),
          })
        }
      }

      if (s.format === "moneyball") {
        for (const p of s.players) {
          if (s.moneyballSlots[p.key] === undefined) continue
          const realId = participantIdMap.get(p.key)
          if (!realId) continue
          await fetch(`/rounds/${roundId}/participants/${realId}`, {
            method: "PATCH",
            headers: { "Content-Type": "application/json" },
            credentials: "include",
            body: JSON.stringify({ lineup_order: s.moneyballSlots[p.key] }),
          })
        }
      }

      onCreated(roundId)
    } catch (err) {
      const msg = err instanceof Error ? err.message : ""
      patch({
        submitting: false,
        submitError: msg && !msg.startsWith("create round:") && !msg.startsWith("create side:") ? msg : "Klarte ikke å opprette runden. Prøv igjen.",
      })
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [state, patch, onCreated])

  const value: WizardCtx = {
    state,
    patch,
    visibleSteps,
    goNext,
    goBack,
    canProceedStep1,
    isAdditionalFlight,
    submit,
    exit: onExit,
  }

  return <Ctx.Provider value={value}>{children}</Ctx.Provider>
}

export function useWizard() {
  const ctx = useContext(Ctx)
  if (!ctx) throw new Error("useWizard must be used within WizardProvider")
  return ctx
}

export { ORDINARY_FORMATS }
