import { toast } from 'bulma-toast'

/**
 * Controller for the cross-dock planning board (page-local, provided via
 * provide('cdBoard', ...) like the label designer's useLabelDesign).
 *
 * Owns: the visible date window, board data (inbound POs / outbound SOs /
 * allocation links), selection, the allocation dialog, the optimistic
 * DatePromised drag-write, and the one-click shipment generation.
 */

export interface CdOrder {
  id: number
  documentNo: string
  dateOrdered: string | null
  datePromised: string | null
  grandTotal: number
  orgId: number
  orgName: string
  warehouseId: number | null
  warehouseName: string
  partnerId: number | null
  partnerName: string
  lineCount: number
  totalQty: number
  openQty: number
  received?: boolean        // inbound only
  isUnfulfillable?: boolean // outbound only
}

export interface CdLink {
  poOrderId: number
  soOrderId: number
  qtyAllocated: number
  soOpenTotal: number
  coveragePct: number
  arrived: boolean
  products: Array<{
    allocId: number
    productId: number
    productName: string
    qtyAllocated: number
    poOpenQty: number
    soOpenQty: number
    poOrderLineId: number
    soOrderLineId: number
  }>
}

export interface CdSelection {
  type: 'order' | 'link'
  lane?: 'in' | 'out'
  id?: number
  poOrderId?: number
  soOrderId?: number
}

const DAY_MS = 86400000

const pad2 = (n: number) => String(n).padStart(2, '0')
export const cdFormatDate = (d: Date) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`

const localMidnight = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate())
// Calendar-day addition (NOT ms addition — that drifts an hour across DST
// changes and would mislabel/misplace days on the board).
const addDays = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n)

export const useCrossdockBoard = () => {
  // ---- date window ----
  const rangeStart = ref<Date>(addDays(new Date(), -3))
  const days = ref(21)
  const rangeEnd = computed(() => addDays(rangeStart.value, days.value - 1))

  // ---- filters ----
  const warehouseId = ref<string | number>('')

  // ---- data ----
  const pending = ref(false)
  const error = ref<string | null>(null)
  const inbound = ref<CdOrder[]>([])
  const outbound = ref<CdOrder[]>([])
  const links = ref<CdLink[]>([])
  const linkTableMissing = ref(false)
  const loadedOnce = ref(false)

  // ---- interaction ----
  const selection = ref<CdSelection | null>(null)
  const dropTargetId = ref<number | null>(null) // opposite-lane truck under a cross-lane drag
  const draggingId = ref<number | null>(null)

  // ---- allocation dialog ----
  const allocDialog = ref<{
    open: boolean
    poOrderId: number
    soOrderId: number
    loading: boolean
    saving: boolean
    rows: Array<{
      productName: string
      sku: string
      poOrderLineId: number
      soOrderLineId: number
      poOpenQty: number
      soOpenQty: number
      poAllocatedOther: number
      soAllocatedOther: number
      existingId: number | null
      existingQty: number
      qty: number
      maxQty: number
    }>
  }>({ open: false, poOrderId: 0, soOrderId: 0, loading: false, saving: false, rows: [] })

  // ---- per-SO shipment generation status ----
  const generateStatus = ref<Record<number, 'idle' | 'loading' | 'success'>>({})

  // ---- date helpers (board data is date-only 'YYYY-MM-DD', parsed DST-safe) ----
  const parseDate = (v: any): Date | null => parseIdempiereTimestamp(v)
  const dayIndex = (v: any): number => {
    const d = parseDate(v)
    if (!d) return 0
    // round absorbs the ±1h DST offset between local midnights
    return Math.round((localMidnight(d).getTime() - rangeStart.value.getTime()) / DAY_MS)
  }
  const dayToDate = (i: number): Date => addDays(rangeStart.value, i)

  // ---- lookups ----
  const inboundById = computed(() => {
    const m: Record<number, CdOrder> = {}
    for (const o of inbound.value) m[o.id] = o
    return m
  })
  const outboundById = computed(() => {
    const m: Record<number, CdOrder> = {}
    for (const o of outbound.value) m[o.id] = o
    return m
  })
  // Only ribbons whose both trucks are visible in the current window get drawn.
  const visibleLinks = computed(() =>
    links.value.filter(l => inboundById.value[l.poOrderId] && outboundById.value[l.soOrderId])
  )
  const linksForOrder = (lane: 'in' | 'out', id: number) =>
    links.value.filter(l => (lane === 'in' ? l.poOrderId === id : l.soOrderId === id))

  /** Outbound truck state derived from its links: null | 'planned' | 'partial' | 'ready' */
  const soLinkState = (id: number): string | null => {
    const ls = linksForOrder('out', id)
    if (!ls.length) return null
    if (ls.every(l => l.arrived)) return 'ready'
    if (ls.some(l => l.arrived)) return 'partial'
    return 'planned'
  }

  // ---- loading ----
  const load = async (silent = false) => {
    if (!silent) { pending.value = true }
    error.value = null
    try {
      const params = new URLSearchParams({
        from: cdFormatDate(rangeStart.value),
        to: cdFormatDate(rangeEnd.value)
      })
      if (warehouseId.value) params.set('warehouseId', String(warehouseId.value))
      const res: any = await $fetch('/api/fulfillment/crossdock/board?' + params.toString(), {
        headers: useRequestHeaders(['cookie'])
      })
      inbound.value = res?.inbound ?? []
      outbound.value = res?.outbound ?? []
      links.value = res?.links ?? []
      linkTableMissing.value = !!res?.linkTableMissing
      loadedOnce.value = true
    } catch (err: any) {
      error.value = err?.data?.message ?? err?.message ?? 'Board konnte nicht geladen werden'
    } finally {
      pending.value = false
    }
  }

  const shiftRange = (deltaDays: number) => {
    rangeStart.value = addDays(rangeStart.value, deltaDays)
    load()
  }
  const setDays = (n: number) => {
    days.value = n
    load()
  }
  const resetRange = () => {
    rangeStart.value = addDays(new Date(), -3)
    load()
  }

  // ---- drag: date change (optimistic write + revert) ----
  const moveOrder = async (lane: 'in' | 'out', orderId: number, newDay: number) => {
    const list = lane === 'in' ? inbound : outbound
    const order = list.value.find(o => o.id === orderId)
    if (!order) return
    const newDate = cdFormatDate(dayToDate(newDay))
    const oldDate = order.datePromised
    if (oldDate && cdFormatDate(parseDate(oldDate) as Date) === newDate) return

    order.datePromised = newDate // optimistic
    try {
      const res: any = await $fetch('/api/fulfillment/crossdock/order-date', {
        method: 'PUT',
        headers: useRequestHeaders(['cookie']),
        body: { id: orderId, datePromised: newDate }
      })
      if (Number(res?.status) !== 200) throw new Error(res?.message || 'update failed')
      toast({ message: `${order.documentNo}: Termin → ${newDate}`, type: 'is-success', duration: 1800 })
    } catch (err: any) {
      order.datePromised = oldDate // revert
      toast({
        message: `${order.documentNo}: Termin konnte nicht geändert werden — ${err?.data?.message ?? err?.message ?? ''}`,
        type: 'is-danger',
        duration: 3500
      })
    }
  }

  // ---- allocation dialog ----
  const openAllocation = async (poOrderId: number, soOrderId: number) => {
    if (linkTableMissing.value) {
      toast({ message: 'Cross-Dock Tabelle fehlt noch in iDempiere (CUST_CrossDockAlloc)', type: 'is-warning', duration: 3500 })
      return
    }
    allocDialog.value = { open: true, poOrderId, soOrderId, loading: true, saving: false, rows: [] }
    try {
      const res: any = await $fetch(
        `/api/fulfillment/crossdock/match-proposal?poOrderId=${poOrderId}&soOrderId=${soOrderId}`,
        { headers: useRequestHeaders(['cookie']) }
      )
      const rows = (res?.rows ?? []).map((r: any) => {
        const maxQty = Math.max(0, Math.min(
          r.poOpenQty - r.poAllocatedOther,
          r.soOpenQty - r.soAllocatedOther
        ))
        return {
          ...r,
          maxQty,
          // pre-fill: keep an existing allocation, otherwise propose the max coverable qty
          qty: r.existingId ? r.existingQty : maxQty
        }
      })
      allocDialog.value.rows = rows
    } catch (err: any) {
      toast({ message: err?.data?.message ?? err?.message ?? 'Vorschlag konnte nicht geladen werden', type: 'is-danger', duration: 3000 })
      allocDialog.value.open = false
    } finally {
      allocDialog.value.loading = false
    }
  }

  const saveAllocation = async () => {
    const d = allocDialog.value
    if (d.saving) return
    d.saving = true
    try {
      const res: any = await $fetch('/api/fulfillment/crossdock/allocations/save', {
        method: 'POST',
        headers: useRequestHeaders(['cookie']),
        body: {
          poOrderId: d.poOrderId,
          soOrderId: d.soOrderId,
          rows: d.rows.map(r => ({
            id: r.existingId,
            poOrderLineId: r.poOrderLineId,
            soOrderLineId: r.soOrderLineId,
            qty: Number(r.qty) || 0
          }))
        }
      })
      if (Number(res?.status) !== 200) throw new Error(res?.message || 'save failed')
      if (Array.isArray(res?.errors) && res.errors.length) {
        toast({ message: `${res.errors.length} Position(en) konnten nicht gespeichert werden`, type: 'is-warning', duration: 3500 })
      } else {
        toast({ message: 'Cross-Dock Zuordnung gespeichert', type: 'is-success', duration: 2000 })
      }
      d.open = false
      await load(true)
    } catch (err: any) {
      toast({ message: err?.data?.message ?? err?.message ?? 'Speichern fehlgeschlagen', type: 'is-danger', duration: 3500 })
    } finally {
      d.saving = false
    }
  }

  const removeLink = async (link: CdLink) => {
    const ids = link.products.map(p => p.allocId).filter(Boolean)
    if (!ids.length) return
    try {
      const res: any = await $fetch('/api/fulfillment/crossdock/allocations/destroy', {
        method: 'DELETE',
        headers: useRequestHeaders(['cookie']),
        body: { ids }
      })
      if (Number(res?.status) !== 200) throw new Error(res?.message || 'delete failed')
      links.value = links.value.filter(l => !(l.poOrderId === link.poOrderId && l.soOrderId === link.soOrderId))
      if (selection.value?.type === 'link') selection.value = null
      toast({ message: 'Cross-Dock Verknüpfung entfernt', type: 'is-success', duration: 2000 })
    } catch (err: any) {
      toast({ message: err?.data?.message ?? err?.message ?? 'Löschen fehlgeschlagen', type: 'is-danger', duration: 3500 })
    }
  }

  // ---- one-click shipment generation (same process call as the SO edit page) ----
  const generateShipment = async (so: CdOrder) => {
    if (generateStatus.value[so.id] === 'loading') return
    if (!so.warehouseId || !so.partnerId) {
      toast({ message: 'Lager und Geschäftspartner werden benötigt', type: 'is-warning', duration: 2500 })
      return
    }
    generateStatus.value = { ...generateStatus.value, [so.id]: 'loading' }
    try {
      const today = cdFormatDate(new Date())
      const res: any = await $fetch('/api/processes/inouts/generate', {
        method: 'POST',
        headers: useRequestHeaders(['cookie']),
        body: {
          organizationId: so.orgId,
          warehouseId: so.warehouseId,
          partnerId: so.partnerId,
          dateAcct: today,
          datePromised: today,
          docStatusId: 'CO',
          isUnconfirmed: false,
          isOneDocument: false,
          inProgress: false,
          isRunJob: false
        }
      })
      if (Number(res?.status) !== 200) throw new Error(res?.message || 'Lieferung konnte nicht erstellt werden')
      generateStatus.value = { ...generateStatus.value, [so.id]: 'success' }
      toast({ message: res?.summary || 'Lieferung(en) erstellt', type: 'is-success', duration: 2500 })
      await load(true)
    } catch (err: any) {
      generateStatus.value = { ...generateStatus.value, [so.id]: 'idle' }
      toast({ message: err?.data?.message ?? err?.message ?? 'Lieferung konnte nicht erstellt werden', type: 'is-danger', duration: 3500 })
    }
  }

  // ---- auto refresh (60s, silent) ----
  let refreshTimer: any = null
  const startAutoRefresh = () => {
    stopAutoRefresh()
    refreshTimer = setInterval(() => { if (!allocDialog.value.open && !draggingId.value) load(true) }, 60000)
  }
  const stopAutoRefresh = () => {
    if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null }
  }

  return {
    // state
    rangeStart, rangeEnd, days, warehouseId,
    pending, error, inbound, outbound, links, linkTableMissing, loadedOnce,
    selection, dropTargetId, draggingId, allocDialog, generateStatus,
    // lookups
    inboundById, outboundById, visibleLinks, linksForOrder, soLinkState,
    // date helpers
    parseDate, dayIndex, dayToDate,
    // actions
    load, shiftRange, setDays, resetRange,
    moveOrder, openAllocation, saveAllocation, removeLink, generateShipment,
    startAutoRefresh, stopAutoRefresh
  }
}
