"use client"

import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useRef,
  useState,
} from "react"

export interface Signature {
  id: string
  name: string
  html: string
}

export interface Identity {
  name: string
  email: string
  defaultSignatureId: string | null
}

export interface Contact {
  name: string
  email: string
}

export interface Attachment {
  id: string
  file: File
  name: string
  size: number
  type: string
}

export type ComposePlacement = "dock" | "inline"

export type ThreadComposeKind = "reply" | "replyAll" | "forward"

export interface ThreadingMeta {
  /** Valeur type Message-Id pour en-tête In-Reply-To */
  inReplyTo: string
  /** Chaîne References (tokens séparés par des espaces) */
  references: string[]
}

/** Snapshot pour détecter les changements dans une réponse / transfert inline */
export interface ThreadDraftBaseline {
  bodyHtml: string
  subject: string
  to: Contact[]
  cc: Contact[]
  bcc: Contact[]
}

/** Brouillon de réponse ou transfert rattaché à une conversation (`Email.id`) */
export interface SavedThreadReplyDraft {
  threadEmailId: string
  threadKind: ThreadComposeKind
  from: Identity
  to: Contact[]
  cc: Contact[]
  bcc: Contact[]
  subject: string
  bodyHtml: string
  threading: ThreadingMeta | null
  showCc: boolean
  showBcc: boolean
  signatureId: string | null
  autoInsertSignature: boolean
}

export interface ComposeState {
  id: string
  from: Identity
  to: Contact[]
  cc: Contact[]
  bcc: Contact[]
  subject: string
  bodyHtml: string
  attachments: Attachment[]
  signatureId: string | null
  autoInsertSignature: boolean
  minimized: boolean
  maximized: boolean
  showFrom: boolean
  showCc: boolean
  showBcc: boolean
  savedAt: number | null
  /** dock = fenêtres en bas à droite ; inline = sous le fil dans la vue message */
  placement: ComposePlacement
  /** Si placement inline : id du mail ouvert (`Email.id`) */
  threadEmailId: string | null
  threadKind?: ThreadComposeKind
  focusToOnMount?: boolean
  /** Réponse / reprise brouillon : focus corps, curseur en tête du message */
  focusBodyOnMount?: boolean
  /** Après passage en fenêtre dock : focus sur le champ Objet une fois */
  focusSubjectOnMount?: boolean
  threading?: ThreadingMeta | null
  /** Référence initiale (nouveau message ou hydratation brouillon) pour savoir si le fil a été modifié */
  threadDraftBaseline: ThreadDraftBaseline | null
  /** Ouvert depuis la carte `savedThreadReplyDrafts` — si fermé sans modif, on conserve la carte */
  threadDraftOpenedFromSaved: boolean
  /** Édition d’un envoi déjà dans « Planifié » (id stable côté placeholder API). */
  editingScheduledId: string | null
  /** ISO date/heure d’envoi affichée et mise à jour par « Planifier ». */
  scheduledSendAtIso: string | null
}

export const SIGNATURES: Signature[] = [
  {
    id: "sig-personal",
    name: "Personnelle",
    html: `<div style="color:#5f6368;font-size:13px"><b>CRIPPLINGDEPRESSION</b><br>redeathray@gmail.com</div>`,
  },
  {
    id: "sig-pro",
    name: "Professionnelle",
    html: `<div style="color:#5f6368;font-size:13px"><b>Eliott Pro</b><br>Software Engineer<br>eliott.pro@company.com<br>+33 6 12 34 56 78</div>`,
  },
]

export const DEFAULT_IDENTITIES: Identity[] = [
  { name: "CRIPPLINGDEPRESSION", email: "redeathray@gmail.com", defaultSignatureId: "sig-personal" },
  { name: "Eliott Pro", email: "eliott.pro@company.com", defaultSignatureId: "sig-pro" },
]

export const MOCK_CONTACTS: Contact[] = [
  { name: "John Jeffrey", email: "john.jeffrey@gmail.com" },
  { name: "Marie Dupont", email: "marie.dupont@outlook.com" },
  { name: "Alice Martin", email: "alice.martin@yahoo.fr" },
  { name: "Bob Wilson", email: "bob.wilson@proton.me" },
  { name: "Clara Zhang", email: "clara.zhang@gmail.com" },
  { name: "David Nakamura", email: "david.nakamura@icloud.com" },
  { name: "Emma Bernard", email: "emma.bernard@hotmail.com" },
  { name: "François Leroy", email: "francois.leroy@free.fr" },
  { name: "Gabriel Santos", email: "gabriel.santos@gmail.com" },
  { name: "Hana Kim", email: "hana.kim@naver.com" },
]

let composeCounter = 0

function createNewCompose(): ComposeState {
  composeCounter++
  const identity = DEFAULT_IDENTITIES[0]
  const sigId = identity.defaultSignatureId
  const sig = sigId ? SIGNATURES.find((s) => s.id === sigId) : null
  const bodyHtml = sig
    ? `<p></p><div id="ultimail-signature"><p>--</p>${sig.html}</div>`
    : ""
  return {
    id: `compose-${Date.now()}-${composeCounter}`,
    from: identity,
    to: [],
    cc: [],
    bcc: [],
    subject: "",
    bodyHtml,
    attachments: [],
    signatureId: sigId,
    autoInsertSignature: true,
    minimized: false,
    maximized: false,
    showFrom: false,
    showCc: false,
    showBcc: false,
    savedAt: null,
    placement: "dock",
    threadEmailId: null,
    threadKind: undefined,
    focusToOnMount: true,
    focusBodyOnMount: false,
    focusSubjectOnMount: false,
    threading: null,
    threadDraftBaseline: null,
    threadDraftOpenedFromSaved: false,
    editingScheduledId: null,
    scheduledSendAtIso: null,
  }
}

export type ComposeOpenPreset = Partial<
  Pick<
    ComposeState,
    | "to"
    | "cc"
    | "bcc"
    | "subject"
    | "bodyHtml"
    | "placement"
    | "threadEmailId"
    | "threadKind"
    | "focusToOnMount"
    | "focusBodyOnMount"
    | "focusSubjectOnMount"
    | "maximized"
    | "threading"
    | "showCc"
    | "showBcc"
    | "from"
    | "signatureId"
    | "autoInsertSignature"
    | "editingScheduledId"
    | "scheduledSendAtIso"
  >
> & {
  /** Hydratation depuis `savedThreadReplyDrafts` */
  openedFromSavedThreadDraft?: boolean
}

function cloneContacts(list: Contact[]): Contact[] {
  return list.map((c) => ({ ...c }))
}

function cloneThreadDraftBaseline(b: ThreadDraftBaseline): ThreadDraftBaseline {
  return {
    bodyHtml: b.bodyHtml,
    subject: b.subject,
    to: cloneContacts(b.to),
    cc: cloneContacts(b.cc),
    bcc: cloneContacts(b.bcc),
  }
}

/** Copie profonde (sauf références `File` des pièces jointes) pour annulation d’envoi différé. */
export function cloneComposeForPendingSend(c: ComposeState): ComposeState {
  return {
    ...c,
    from: { ...c.from },
    to: cloneContacts(c.to),
    cc: cloneContacts(c.cc),
    bcc: cloneContacts(c.bcc),
    attachments: c.attachments.map((a) => ({ ...a })),
    threading: c.threading ? { ...c.threading } : null,
    threadDraftBaseline: c.threadDraftBaseline
      ? cloneThreadDraftBaseline(c.threadDraftBaseline)
      : null,
  }
}

export function makeThreadDraftBaseline(
  c: Pick<ComposeState, "bodyHtml" | "subject" | "to" | "cc" | "bcc">
): ThreadDraftBaseline {
  return {
    bodyHtml: c.bodyHtml,
    subject: c.subject,
    to: cloneContacts(c.to),
    cc: cloneContacts(c.cc),
    bcc: cloneContacts(c.bcc),
  }
}

function contactsEqual(a: Contact[], b: Contact[]): boolean {
  if (a.length !== b.length) return false
  return a.every(
    (x, i) =>
      x.email === b[i]?.email && x.name === (b[i]?.name ?? "")
  )
}

export function isThreadDraftModified(c: ComposeState): boolean {
  const baseline = c.threadDraftBaseline
  if (
    !baseline ||
    c.placement !== "inline" ||
    !c.threadEmailId
  ) {
    return false
  }
  return (
    c.bodyHtml !== baseline.bodyHtml ||
    c.subject !== baseline.subject ||
    !contactsEqual(c.to, baseline.to) ||
    !contactsEqual(c.cc, baseline.cc) ||
    !contactsEqual(c.bcc, baseline.bcc)
  )
}

export function composeToSavedThreadDraft(c: ComposeState): SavedThreadReplyDraft {
  const tid = c.threadEmailId!
  const kind = c.threadKind ?? "reply"
  return {
    threadEmailId: tid,
    threadKind: kind,
    from: c.from,
    to: cloneContacts(c.to),
    cc: cloneContacts(c.cc),
    bcc: cloneContacts(c.bcc),
    subject: c.subject,
    bodyHtml: c.bodyHtml,
    threading: c.threading ?? null,
    showCc: c.showCc,
    showBcc: c.showBcc,
    signatureId: c.signatureId,
    autoInsertSignature: c.autoInsertSignature,
  }
}

export function savedThreadDraftToComposePreset(
  d: SavedThreadReplyDraft
): ComposeOpenPreset {
  return {
    from: d.from,
    to: d.to,
    cc: d.cc,
    bcc: d.bcc,
    subject: d.subject,
    bodyHtml: d.bodyHtml,
    placement: "inline",
    threadEmailId: d.threadEmailId,
    threadKind: d.threadKind,
    threading: d.threading ?? undefined,
    showCc: d.showCc,
    showBcc: d.showBcc,
    signatureId: d.signatureId ?? undefined,
    autoInsertSignature: d.autoInsertSignature,
    focusToOnMount: d.threadKind === "forward",
    focusBodyOnMount: d.threadKind !== "forward",
    openedFromSavedThreadDraft: true,
  }
}

function mergeComposeFromPreset(
  base: ComposeState,
  preset: ComposeOpenPreset
): ComposeState {
  return {
    ...base,
    from: preset.from ?? base.from,
    to: preset.to ?? base.to,
    cc: preset.cc ?? base.cc,
    bcc: preset.bcc ?? base.bcc,
    subject: preset.subject ?? base.subject,
    bodyHtml: preset.bodyHtml ?? base.bodyHtml,
    placement: preset.placement ?? base.placement,
    threadEmailId: preset.threadEmailId ?? base.threadEmailId,
    threadKind: preset.threadKind ?? base.threadKind,
    focusToOnMount: preset.focusToOnMount ?? base.focusToOnMount,
    focusBodyOnMount: preset.focusBodyOnMount ?? base.focusBodyOnMount,
    focusSubjectOnMount:
      preset.focusSubjectOnMount ?? base.focusSubjectOnMount,
    maximized: preset.maximized ?? base.maximized,
    threading: preset.threading !== undefined ? preset.threading : base.threading,
    showCc: preset.showCc ?? base.showCc,
    showBcc: preset.showBcc ?? base.showBcc,
    signatureId:
      preset.signatureId !== undefined ? preset.signatureId : base.signatureId,
    autoInsertSignature:
      preset.autoInsertSignature !== undefined
        ? preset.autoInsertSignature
        : base.autoInsertSignature,
    editingScheduledId:
      preset.editingScheduledId !== undefined
        ? preset.editingScheduledId
        : base.editingScheduledId,
    scheduledSendAtIso:
      preset.scheduledSendAtIso !== undefined
        ? preset.scheduledSendAtIso
        : base.scheduledSendAtIso,
  }
}

function withInlineThreadDraftMeta(
  merged: ComposeState,
  openedFromSaved: boolean
): ComposeState {
  if (merged.placement !== "inline" || !merged.threadEmailId) {
    return {
      ...merged,
      threadDraftBaseline: null,
      threadDraftOpenedFromSaved: false,
    }
  }
  return {
    ...merged,
    threadDraftBaseline: makeThreadDraftBaseline(merged),
    threadDraftOpenedFromSaved: openedFromSaved,
  }
}

function applySavedDraftsAfterClosingInline(
  prevDrafts: Record<string, SavedThreadReplyDraft>,
  w: ComposeState
): Record<string, SavedThreadReplyDraft> {
  const tid = w.threadEmailId
  if (!tid || w.placement !== "inline") return prevDrafts
  const next: Record<string, SavedThreadReplyDraft> = { ...prevDrafts }
  if (isThreadDraftModified(w)) {
    next[tid] = composeToSavedThreadDraft(w)
  } else if (!w.threadDraftOpenedFromSaved) {
    delete next[tid]
  }
  return next
}

export type ComposeActionsContextValue = {
  openCompose: () => void
  openComposeWithInitial: (preset: ComposeOpenPreset) => void
  /** Réouvre une fenêtre après annulation d’un envoi en cours (nouvel id, baseline recalculée). */
  restoreComposeFromSnapshot: (snapshot: ComposeState) => void
  /** Met à jour une fenêtre existante avec un preset (ex. changer Répondre → Transférer en inline). */
  applyComposePreset: (id: string, preset: ComposeOpenPreset) => void
  closeCompose: (id: string, opts?: { sent?: boolean; discardThreadReplyDraft?: boolean }) => void
  closeAllInlineComposes: () => void
  /** Ferme les inline d’autres fils ; conserve celui du fil `keepThreadEmailId` (vue message). */
  pruneInlineComposesToOpenThread: (keepThreadEmailId: string) => void
  updateCompose: (id: string, patch: Partial<ComposeState>) => void
  toggleMinimize: (id: string) => void
  toggleMaximize: (id: string) => void
  saveDraft: (id: string) => void
}

export type ComposeDraftsContextValue = {
  /** Brouillons de réponse/transfert par id de conversation */
  savedThreadReplyDrafts: Record<string, SavedThreadReplyDraft>
}

export type ComposeWindowsContextValue = {
  composeWindows: ComposeState[]
}

export type ComposeContextValue = ComposeActionsContextValue &
  ComposeDraftsContextValue &
  ComposeWindowsContextValue

const ComposeActionsContext = createContext<ComposeActionsContextValue | null>(null)
const ComposeDraftsContext = createContext<ComposeDraftsContextValue | null>(null)
const ComposeWindowsContext = createContext<ComposeWindowsContextValue | null>(null)

export function ComposeProvider({ children }: { children: React.ReactNode }) {
  const [composeWindows, setComposeWindows] = useState<ComposeState[]>([])
  const [savedThreadReplyDrafts, setSavedThreadReplyDrafts] = useState<
    Record<string, SavedThreadReplyDraft>
  >({})
  const autoSaveTimers = useRef<Map<string, NodeJS.Timeout>>(new Map())
  const composeWindowsRef = useRef<ComposeState[]>(composeWindows)
  composeWindowsRef.current = composeWindows

  const openCompose = useCallback(() => {
    const newCompose = createNewCompose()
    setComposeWindows((prev) => {
      const updated = prev.map((w) =>
        !w.minimized ? { ...w, minimized: false } : w
      )
      return [...updated, newCompose]
    })
  }, [])

  const openComposeWithInitial = useCallback((preset: ComposeOpenPreset) => {
    const baseMerged = mergeComposeFromPreset(createNewCompose(), preset)
    const openedFromSaved = preset.openedFromSavedThreadDraft === true
    const merged = withInlineThreadDraftMeta(baseMerged, openedFromSaved)

    if (merged.placement === "inline" && merged.threadEmailId && !openedFromSaved) {
      setSavedThreadReplyDrafts((d) => {
        const next = { ...d }
        delete next[merged.threadEmailId!]
        return next
      })
    }

    const prevSnap = composeWindowsRef.current
    if (merged.placement === "inline" && merged.threadEmailId) {
      const victim = prevSnap.find(
        (w) =>
          w.placement === "inline" &&
          w.threadEmailId === merged.threadEmailId
      )
      if (victim && isThreadDraftModified(victim)) {
        setSavedThreadReplyDrafts((d) =>
          applySavedDraftsAfterClosingInline(d, victim)
        )
      }
    }

    setComposeWindows((prev) => {
      let next = prev
      if (merged.placement === "inline" && merged.threadEmailId) {
        next = prev.filter(
          (w) =>
            !(
              w.placement === "inline" &&
              w.threadEmailId === merged.threadEmailId
            )
        )
      }
      const updated = next.map((w) =>
        !w.minimized ? { ...w, minimized: false } : w
      )
      return [...updated, merged]
    })
  }, [])

  const restoreComposeFromSnapshot = useCallback((snapshot: ComposeState) => {
    composeCounter++
    const newId = `compose-${Date.now()}-${composeCounter}`
    const merged: ComposeState = {
      ...snapshot,
      id: newId,
      minimized: false,
      maximized: false,
      threadDraftBaseline:
        snapshot.placement === "inline" && snapshot.threadEmailId
          ? makeThreadDraftBaseline(snapshot)
          : null,
      threadDraftOpenedFromSaved: false,
    }

    const openedFromSaved = false
    if (merged.placement === "inline" && merged.threadEmailId && !openedFromSaved) {
      setSavedThreadReplyDrafts((d) => {
        const next = { ...d }
        delete next[merged.threadEmailId!]
        return next
      })
    }

    const prevSnap = composeWindowsRef.current
    if (merged.placement === "inline" && merged.threadEmailId) {
      const victim = prevSnap.find(
        (w) =>
          w.placement === "inline" &&
          w.threadEmailId === merged.threadEmailId
      )
      if (victim && isThreadDraftModified(victim)) {
        setSavedThreadReplyDrafts((d) =>
          applySavedDraftsAfterClosingInline(d, victim)
        )
      }
    }

    setComposeWindows((prev) => {
      let next = prev
      if (merged.placement === "inline" && merged.threadEmailId) {
        next = prev.filter(
          (w) =>
            !(
              w.placement === "inline" &&
              w.threadEmailId === merged.threadEmailId
            )
        )
      }
      const updated = next.map((w) =>
        !w.minimized ? { ...w, minimized: false } : w
      )
      return [...updated, merged]
    })
  }, [])

  const applyComposePreset = useCallback((id: string, preset: ComposeOpenPreset) => {
    setComposeWindows((prev) =>
      prev.map((w) => {
        if (w.id !== id) return w
        const m = mergeComposeFromPreset(w, preset)
        if (m.placement === "inline" && m.threadEmailId) {
          return withInlineThreadDraftMeta(m, w.threadDraftOpenedFromSaved)
        }
        return {
          ...m,
          threadDraftBaseline: null,
          threadDraftOpenedFromSaved: false,
        }
      })
    )
  }, [])

  const closeAllInlineComposes = useCallback(() => {
    const prev = composeWindowsRef.current
    setSavedThreadReplyDrafts((d) => {
      let next = d
      for (const w of prev) {
        if (w.placement === "inline" && w.threadEmailId) {
          next = applySavedDraftsAfterClosingInline(next, w)
        }
      }
      return next
    })
    setComposeWindows((p) => p.filter((x) => x.placement !== "inline"))
  }, [])

  const pruneInlineComposesToOpenThread = useCallback(
    (keepThreadEmailId: string) => {
      const prev = composeWindowsRef.current
      setSavedThreadReplyDrafts((d) => {
        let next = d
        for (const w of prev) {
          if (
            w.placement === "inline" &&
            w.threadEmailId &&
            w.threadEmailId !== keepThreadEmailId
          ) {
            next = applySavedDraftsAfterClosingInline(next, w)
          }
        }
        return next
      })
      setComposeWindows((p) =>
        p.filter(
          (w) =>
            w.placement !== "inline" ||
            w.threadEmailId === keepThreadEmailId
        )
      )
    },
    []
  )

  const closeCompose = useCallback((id: string, opts?: { sent?: boolean; discardThreadReplyDraft?: boolean }) => {
    const timer = autoSaveTimers.current.get(id)
    if (timer) {
      clearTimeout(timer)
      autoSaveTimers.current.delete(id)
    }
    const w = composeWindowsRef.current.find((x) => x.id === id)
    if (w?.placement === "inline" && w.threadEmailId) {
      if (opts?.sent || opts?.discardThreadReplyDraft) {
        setSavedThreadReplyDrafts((d) => {
          const next = { ...d }
          delete next[w.threadEmailId!]
          return next
        })
      } else {
        setSavedThreadReplyDrafts((d) => applySavedDraftsAfterClosingInline(d, w))
      }
    }
    setComposeWindows((prev) => prev.filter((x) => x.id !== id))
  }, [])

  const updateCompose = useCallback((id: string, patch: Partial<ComposeState>) => {
    setComposeWindows((prev) =>
      prev.map((w) => (w.id === id ? { ...w, ...patch } : w))
    )

    const existingTimer = autoSaveTimers.current.get(id)
    if (existingTimer) clearTimeout(existingTimer)
    const timer = setTimeout(() => {
      setComposeWindows((prev) =>
        prev.map((w) =>
          w.id === id ? { ...w, savedAt: Date.now() } : w
        )
      )
      autoSaveTimers.current.delete(id)
    }, 3000)
    autoSaveTimers.current.set(id, timer)
  }, [])

  const toggleMinimize = useCallback((id: string) => {
    setComposeWindows((prev) =>
      prev.map((w) =>
        w.id === id
          ? { ...w, minimized: !w.minimized, maximized: false }
          : w
      )
    )
  }, [])

  const toggleMaximize = useCallback((id: string) => {
    setComposeWindows((prev) =>
      prev.map((w) =>
        w.id === id
          ? { ...w, maximized: !w.maximized, minimized: false }
          : w
      )
    )
  }, [])

  const saveDraft = useCallback((id: string) => {
    setComposeWindows((prev) =>
      prev.map((w) =>
        w.id === id ? { ...w, savedAt: Date.now() } : w
      )
    )
  }, [])

  const actionsValue = useMemo<ComposeActionsContextValue>(
    () => ({
      openCompose,
      openComposeWithInitial,
      restoreComposeFromSnapshot,
      applyComposePreset,
      closeCompose,
      closeAllInlineComposes,
      pruneInlineComposesToOpenThread,
      updateCompose,
      toggleMinimize,
      toggleMaximize,
      saveDraft,
    }),
    [
      openCompose,
      openComposeWithInitial,
      restoreComposeFromSnapshot,
      applyComposePreset,
      closeCompose,
      closeAllInlineComposes,
      pruneInlineComposesToOpenThread,
      updateCompose,
      toggleMinimize,
      toggleMaximize,
      saveDraft,
    ]
  )

  const draftsValue = useMemo<ComposeDraftsContextValue>(
    () => ({ savedThreadReplyDrafts }),
    [savedThreadReplyDrafts]
  )

  const windowsValue = useMemo<ComposeWindowsContextValue>(
    () => ({ composeWindows }),
    [composeWindows]
  )

  return (
    <ComposeActionsContext.Provider value={actionsValue}>
      <ComposeDraftsContext.Provider value={draftsValue}>
        <ComposeWindowsContext.Provider value={windowsValue}>
          {children}
        </ComposeWindowsContext.Provider>
      </ComposeDraftsContext.Provider>
    </ComposeActionsContext.Provider>
  )
}

export function useComposeActions(): ComposeActionsContextValue {
  const ctx = useContext(ComposeActionsContext)
  if (!ctx) throw new Error("useComposeActions must be used inside <ComposeProvider>")
  return ctx
}

export function useComposeDrafts(): ComposeDraftsContextValue {
  const ctx = useContext(ComposeDraftsContext)
  if (!ctx) throw new Error("useComposeDrafts must be used inside <ComposeProvider>")
  return ctx
}

export function useComposeWindows(): ComposeWindowsContextValue {
  const ctx = useContext(ComposeWindowsContext)
  if (!ctx) throw new Error("useComposeWindows must be used inside <ComposeProvider>")
  return ctx
}

/** Merged read — rerenders when windows, drafts, or any action identity changes. Prefer split hooks for perf. */
export function useCompose(): ComposeContextValue {
  return {
    ...useComposeActions(),
    ...useComposeDrafts(),
    ...useComposeWindows(),
  }
}
