import { ref, computed } from 'vue'
import { HUB_URL, CATALOG_CATEGORIES, RESIDENTIAL_PRESETS, INTERNET_HERO_TIERS, INTERNET_HERO_CODES, TV_HERO_TIERS, TV_HERO_CODES, PREMIUM_SPORTS_CHANNELS, TV_PREMIUM_SURCHARGE_CODE, TV_ALC_OVERAGE_CODE } from 'src/data/wizard-constants'

// Premium channel lookup — keyed by premium_group id → catalog entry. Used to
// compute surcharges and pick-costs without re-walking PREMIUM_SPORTS_CHANNELS.
const PREMIUM_BY_GROUP = new Map(PREMIUM_SPORTS_CHANNELS.map(p => [p.id, p]))

// Shared "one truck roll" collapse — when multiple templates include the same
// logical task (e.g. fiber install visit), we keep a single step and rewire
// dependencies so the merged step's successors point at the right index.
// merge_key is the stable identifier; we fall back to subject for legacy
// templates that don't yet declare one.

const FALLBACK_CATALOG = [
  { item_code: 'INT-100', item_name: 'Internet 100 Mbps', rate: 49.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install' },
  { item_code: 'INT-300', item_name: 'Internet 300 Mbps', rate: 69.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install' },
  { item_code: 'INT-500', item_name: 'Internet 500 Mbps', rate: 89.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install' },
  { item_code: 'INT-1000', item_name: 'Internet 1 Gbps', rate: 109.99, billing_type: 'Mensuel', service_category: 'Internet', requires_visit: true, project_template_id: 'fiber_install' },
  { item_code: 'TEL-RES', item_name: 'Téléphonie résidentielle', rate: 19.99, billing_type: 'Mensuel', service_category: 'Téléphonie', requires_visit: true, project_template_id: 'phone_service' },
  { item_code: 'TEL-ILL', item_name: 'Téléphonie illimitée CA/US', rate: 29.99, billing_type: 'Mensuel', service_category: 'Téléphonie', requires_visit: true, project_template_id: 'phone_service' },
  { item_code: 'BDL-DUO-300', item_name: 'Duo Internet 300 + Téléphonie', rate: 79.99, billing_type: 'Mensuel', service_category: 'Bundle', requires_visit: true, project_template_id: 'fiber_install', is_bundle: true },
  { item_code: 'BDL-DUO-500', item_name: 'Duo Internet 500 + Téléphonie', rate: 99.99, billing_type: 'Mensuel', service_category: 'Bundle', requires_visit: true, project_template_id: 'fiber_install', is_bundle: true },
  { item_code: 'BDL-TRIO', item_name: 'Trio Internet 500 + Tél + IPTV', rate: 119.99, billing_type: 'Mensuel', service_category: 'Bundle', requires_visit: true, project_template_id: 'fiber_install', is_bundle: true },
  { item_code: 'EQP-ROUTER', item_name: 'Routeur WiFi 6', rate: 149.99, billing_type: 'Unique', service_category: 'Équipement' },
  { item_code: 'FEE-INSTALL', item_name: 'Frais d\'installation', rate: 75.00, billing_type: 'Unique', service_category: 'Frais' },
  { item_code: 'FEE-ACTIV', item_name: 'Frais d\'activation', rate: 25.00, billing_type: 'Unique', service_category: 'Frais' },
]

export function useWizardCatalog ({ orderItems, wizardSteps, templates, templateLoadedFor }) {
  const catalogProducts = ref([])
  const catalogLoading = ref(false)
  const catalogFilter = ref('Tous')
  const lastMergeCount = ref(0)
  const mergedTemplateLabels = ref([])

  const filteredCatalog = computed(() => {
    if (catalogFilter.value === 'Tous') return catalogProducts.value
    return catalogProducts.value.filter(p => p.service_category === catalogFilter.value)
  })

  async function loadCatalog () {
    if (catalogProducts.value.length) return
    catalogLoading.value = true
    try {
      const res = await fetch(`${HUB_URL}/api/catalog`)
      const data = await res.json()
      if (data.ok && data.items) {
        catalogProducts.value = data.items
      }
    } catch (e) {
      console.warn('[Wizard] Catalog load failed:', e.message)
      catalogProducts.value = [...FALLBACK_CATALOG]
    } finally {
      catalogLoading.value = false
    }
  }

  function addCatalogItem (product) {
    orderItems.value.push({
      item_code: product.item_code,
      item_name: product.item_name,
      qty: 1,
      rate: product.rate,
      regular_price: 0,
      billing: product.billing_type === 'Mensuel' || product.billing_type === 'Annuel' ? 'recurring' : 'onetime',
      billing_interval: product.billing_type === 'Annuel' ? 'Year' : 'Month',
      contract_months: 12,
      project_template_id: product.project_template_id || '',
      requires_visit: product.requires_visit || false,
    })
    if (product.project_template_id && !templateLoadedFor.value.has(product.project_template_id)) {
      loadTemplateFromItem({ project_template_id: product.project_template_id })
    }
  }

  // ── Égaler un prix concurrent (terrain) ──────────────────────────────────
  // Le concurrent affiche un TOTAL mensuel (tous services − rabais). On calcule le rabais promotionnel
  // récurrent (SKU rab-x) à appliquer SUR L'INTERNET pour que notre total mensuel = le sien, sur `months` (déf. 24).
  const COMP_REBATE_CODE = 'RAB_X'
  function recurringBase () { return orderItems.value.filter(i => i.billing === 'recurring' && i.item_code !== COMP_REBATE_CODE).reduce((s, i) => s + ((i.qty || 1) * i.rate), 0) }
  function clearCompetitor () { orderItems.value = orderItems.value.filter(i => i.item_code !== COMP_REBATE_CODE) }
  function matchCompetitor (competitorMonthly, months = 24) {
    const target = Number(competitorMonthly)
    const base = Math.round(recurringBase() * 100) / 100
    clearCompetitor()
    if (!(target >= 0) || target >= base) return { ok: false, base, rebate: 0, target } // rien à rabattre (concurrent ≥ notre prix)
    const hero = orderItems.value.find(i => INTERNET_HERO_CODES.has(i.item_code) && !i.is_rebate)
    const rebate = Math.round((base - target) * 100) / 100
    orderItems.value.push({
      item_code: COMP_REBATE_CODE, item_name: 'Rabais concurrentiel (' + months + ' mois)',
      qty: 1, rate: -rebate, regular_price: 0, billing: 'recurring', billing_interval: 'Month', contract_months: months,
      is_rebate: true, applies_to_item: hero ? hero.item_code : '',
    })
    return { ok: true, base, rebate, target, applied_to: hero ? hero.item_name : null }
  }

  // Single-select hero tier swap — generic over a set of codes. Clicking a
  // tier REPLACES whatever tier from the same family is in the cart (doesn't
  // stack). Re-clicking the active tier toggles it off. `service_type` and
  // `family_codes` scope the swap so Internet and TV switchers don't stomp
  // each other. Auto-cleans shared templates (fiber_install) when no item
  // from that family still needs it.
  // When `channelSelection` is supplied (TV Mix tiers), we also emit premium
  // surcharges (one per premium_group) and an à-la-carte overage line when
  // the selection exceeds tier.picks_allowed.
  function selectHeroTierGeneric ({ tier, familyCodes, serviceType, tiers, channelSelection }) {
    const currentHeroCode = orderItems.value.find(i => familyCodes.has(i.item_code) && !i.is_rebate)?.item_code
    const isToggleOff = currentHeroCode === tier.code && !channelSelection

    const extraCodesToClear = serviceType === 'tv'
      ? [TV_PREMIUM_SURCHARGE_CODE, TV_ALC_OVERAGE_CODE]
      : []
    orderItems.value = orderItems.value.filter(i =>
      !familyCodes.has(i.item_code) && !extraCodesToClear.includes(i.item_code),
    )

    // Share the fiber_install template across Internet tiers — only unload it
    // when no remaining order item references it.
    const sharedTpl = tier.items.find(i => i.project_template_id)?.project_template_id
    if (sharedTpl) {
      const stillNeeded = orderItems.value.some(i => i.project_template_id === sharedTpl)
      if (!stillNeeded && templateLoadedFor.value.has(sharedTpl)) removeTemplate(sharedTpl)
    }

    if (isToggleOff) {
      return
    }

    const supplementCode = serviceType === 'tv'
      ? tier.items.find(i => i.item_code && i.item_code.startsWith('TV-MIX'))?.item_code
      : null

    for (const i of tier.items) {
      const isSupplement = supplementCode && i.item_code === supplementCode
      orderItems.value.push({
        item_code: i.item_code,
        item_name: i.item_name,
        qty: 1,
        rate: i.rate,
        regular_price: i.regular_price || 0,
        billing: i.billing,
        billing_interval: i.billing_interval || 'Month',
        contract_months: i.contract_months || 24,
        project_template_id: i.project_template_id || '',
        service_type: serviceType,
        combo_eligible: true,
        is_rebate: !!i.is_rebate,
        applies_to_item: i.applies_to_item || '',
        tv_tier_id: serviceType === 'tv' ? tier.id : undefined,
        tv_channels: isSupplement && channelSelection ? channelSelection.map(c => c.name) : undefined,
      })
    }

    // Emit premium surcharges + overage for TV Mix tiers.
    if (serviceType === 'tv' && channelSelection && channelSelection.length) {
      const seenGroups = new Set()
      for (const ch of channelSelection) {
        if (!ch.premium_group || seenGroups.has(ch.premium_group)) continue
        seenGroups.add(ch.premium_group)
        const premium = PREMIUM_BY_GROUP.get(ch.premium_group)
        if (!premium) continue
        orderItems.value.push({
          item_code: TV_PREMIUM_SURCHARGE_CODE,
          item_name: `Premium ${premium.name}`,
          qty: 1,
          rate: Number(premium.surcharge) || 0,
          regular_price: 0,
          billing: 'recurring',
          billing_interval: 'Month',
          contract_months: 24,
          service_type: 'tv',
          combo_eligible: false,
          premium_group: premium.id,
          applies_to_item: supplementCode || '',
        })
      }

      // Overage: one aggregated à-la-carte line when the selection exceeds picks.
      const picksUsed = channelSelection.reduce((n, c) => n + (c.pick_cost || 1), 0)
      const picksAllowed = tier.picks_allowed || 0
      const overage = Math.max(0, picksUsed - picksAllowed)
      if (overage > 0) {
        orderItems.value.push({
          item_code: TV_ALC_OVERAGE_CODE,
          item_name: `Chaînes additionnelles (à la carte)`,
          qty: overage,
          rate: 0,
          regular_price: 0,
          billing: 'recurring',
          billing_interval: 'Month',
          contract_months: 24,
          service_type: 'tv',
          combo_eligible: false,
          applies_to_item: supplementCode || '',
          pricing_pending: true,
        })
      }
    }

    const tplId = tier.items.find(i => i.project_template_id)?.project_template_id
    if (tplId && !templateLoadedFor.value.has(tplId)) {
      loadTemplateFromItem({ project_template_id: tplId })
    }
  }

  const selectHeroTier = (tier) => selectHeroTierGeneric({
    tier, familyCodes: INTERNET_HERO_CODES, serviceType: 'internet', tiers: INTERNET_HERO_TIERS,
  })
  const selectTvTier = (tier, channelSelection) => selectHeroTierGeneric({
    tier, familyCodes: TV_HERO_CODES, serviceType: 'tv', tiers: TV_HERO_TIERS, channelSelection,
  })

  const selectedHeroTier = computed(() => {
    const code = orderItems.value.find(i => INTERNET_HERO_CODES.has(i.item_code) && !i.is_rebate)?.item_code
    if (!code) return null
    return INTERNET_HERO_TIERS.find(t => t.code === code) || null
  })

  // TV current selection — detect by tv_tier_id stamped on the first TV line.
  // Falls back to code-matching in case of older persisted data.
  const selectedTvTier = computed(() => {
    const first = orderItems.value.find(i => TV_HERO_CODES.has(i.item_code))
    if (!first) return null
    if (first.tv_tier_id) return TV_HERO_TIERS.find(t => t.id === first.tv_tier_id) || null
    return TV_HERO_TIERS.find(t => t.code === first.item_code) || null
  })

  // Apply/unapply a service preset — chip-style toggle.
  // Clicking a preset whose items are already in the cart REMOVES those items
  // and unloads the associated template. Otherwise adds missing items.
  // This fixes the earlier UX where an "added" preset had no click-off path,
  // forcing operators to delete lines one by one.
  function applyPreset (preset) {
    const presetCodes = new Set(preset.items.map(i => i.item_code).filter(Boolean))
    const alreadyApplied = orderItems.value.some(i => presetCodes.has(i.item_code))

    if (alreadyApplied) {
      orderItems.value = orderItems.value.filter(i => !presetCodes.has(i.item_code))
      const tplId = preset.items.find(i => i.project_template_id)?.project_template_id
      if (tplId && templateLoadedFor.value.has(tplId)) {
        removeTemplate(tplId)
      }
      return
    }

    const existingCodes = new Set(orderItems.value.map(i => i.item_code).filter(Boolean))
    for (const i of preset.items) {
      if (i.item_code && existingCodes.has(i.item_code)) continue
      orderItems.value.push({
        item_code: i.item_code,
        item_name: i.item_name,
        qty: 1,
        rate: i.rate,
        regular_price: i.regular_price || 0,
        billing: i.billing,
        billing_interval: i.billing_interval || 'Month',
        contract_months: i.contract_months || 12,
        project_template_id: i.project_template_id || '',
        service_type: preset.service_type || '',
        combo_eligible: !!preset.combo_eligible,
        // Forward rebate metadata so the invoice Jinja can net the line into
        // its parent, and so per-row overrides persist across save/reload.
        is_rebate: !!i.is_rebate,
        applies_to_item: i.applies_to_item || '',
      })
    }
    const tplId = preset.items.find(i => i.project_template_id)?.project_template_id
    if (tplId && !templateLoadedFor.value.has(tplId)) {
      loadTemplateFromItem({ project_template_id: tplId })
    }
  }

  // NOTE: auto-combo rebate was removed. Combo rebates (legacy RAB2X/RAB3X/
  // RAB4X, -5/-10/-15$) are now added MANUALLY by the sales rep from the
  // catalog — the wizard no longer inserts them behind the scenes.

  function stepKey (step) { return step.merge_key || step.subject }

  // Merge a template's steps into wizardSteps, dedup'ing by merge_key and
  // remapping depends_on_step indexes so dependencies still point at the
  // correct (possibly merged) predecessor. Returns the count of steps that
  // collapsed into pre-existing ones.
  function mergeTemplateSteps (tpl) {
    const keyToIdx = new Map()
    wizardSteps.value.forEach((s, i) => { keyToIdx.set(stepKey(s), i) })

    // First pass: decide for each template step whether it matches an
    // existing step or will be appended, so depends_on_step can be remapped
    // correctly before we actually mutate wizardSteps.
    const finalIdx = []
    const toAppend = []
    let nextIdx = wizardSteps.value.length
    let mergedCount = 0

    for (let i = 0; i < tpl.steps.length; i++) {
      const step = tpl.steps[i]
      const key = stepKey(step)
      if (keyToIdx.has(key)) {
        const existingIdx = keyToIdx.get(key)
        const existing = wizardSteps.value[existingIdx]
        const sources = new Set(existing.source_templates || [])
        sources.add(tpl.id)
        existing.source_templates = Array.from(sources)
        finalIdx.push(existingIdx)
        mergedCount++
      } else {
        finalIdx.push(nextIdx)
        toAppend.push({ step, at: nextIdx })
        keyToIdx.set(key, nextIdx)
        nextIdx++
      }
    }

    // Second pass: push new steps with remapped dependencies.
    for (const { step } of toAppend) {
      const dep = step.depends_on_step != null ? finalIdx[step.depends_on_step] : null
      wizardSteps.value.push({
        merge_key: stepKey(step),
        subject: step.subject,
        job_type: step.job_type,
        priority: step.priority,
        duration_h: step.duration_h,
        assigned_group: step.assigned_group || '',
        depends_on_step: dep,
        scheduled_date: '',
        on_open_webhook: step.on_open_webhook || '',
        on_close_webhook: step.on_close_webhook || '',
        source_templates: [tpl.id],
      })
    }

    return mergedCount
  }

  // Remove all steps that originated from `templateId`. Steps shared with
  // another active bundle keep their other sources; only orphans are deleted.
  // depends_on_step indexes are remapped to survive the compaction.
  function removeTemplate (templateId) {
    if (!templateLoadedFor.value.has(templateId)) return
    const oldToNew = new Map()
    const filtered = []
    wizardSteps.value.forEach((step, oldIdx) => {
      const sources = (step.source_templates || []).filter(id => id !== templateId)
      if (sources.length === 0) return
      oldToNew.set(oldIdx, filtered.length)
      filtered.push({ ...step, source_templates: sources })
    })
    for (const step of filtered) {
      if (step.depends_on_step != null) {
        const newDep = oldToNew.get(step.depends_on_step)
        step.depends_on_step = newDep != null ? newDep : null
      }
    }
    wizardSteps.value = filtered
    const next = new Set(templateLoadedFor.value)
    next.delete(templateId)
    templateLoadedFor.value = next
  }

  function toggleTemplate (templateId, enable) {
    if (enable) {
      if (!templateLoadedFor.value.has(templateId)) {
        loadTemplateFromItem({ project_template_id: templateId })
      }
    } else {
      removeTemplate(templateId)
    }
  }

  // Per-step uncheck inside a bundle. Detaches the bundle from the step's
  // sources; if no other bundle claims it, the step is deleted outright.
  // Remaps depends_on_step so survivors still line up.
  function removeStepFromBundle (templateId, mergeKey) {
    const oldToNew = new Map()
    const filtered = []
    wizardSteps.value.forEach((step, oldIdx) => {
      const key = step.merge_key || step.subject
      if (key === mergeKey) {
        const sources = (step.source_templates || []).filter(id => id !== templateId)
        if (sources.length === 0) return
        oldToNew.set(oldIdx, filtered.length)
        filtered.push({ ...step, source_templates: sources })
      } else {
        oldToNew.set(oldIdx, filtered.length)
        filtered.push(step)
      }
    })
    for (const step of filtered) {
      if (step.depends_on_step != null) {
        const newDep = oldToNew.get(step.depends_on_step)
        step.depends_on_step = newDep != null ? newDep : null
      }
    }
    wizardSteps.value = filtered
  }

  // Ad-hoc step injection — tagged with the owning bundle so it follows the
  // bundle toggle lifecycle (off → removed, on → stays if it wasn't re-added
  // elsewhere). Uses a synthesised merge_key so it can't collide with a
  // catalog template step.
  function addStepToBundle (templateId, stepData) {
    wizardSteps.value.push({
      merge_key: stepData.merge_key || `custom_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
      subject: stepData.subject,
      job_type: stepData.job_type || 'Autre',
      priority: stepData.priority || 'medium',
      duration_h: Number(stepData.duration_h) || 0.5,
      assigned_group: stepData.assigned_group || '',
      depends_on_step: null,
      scheduled_date: '',
      on_open_webhook: '',
      on_close_webhook: '',
      source_templates: [templateId],
    })
  }

  // Grouping used by the "Étapes chargées" summary card on step 2.
  // A step with more than one source template is flagged `shared` so the UI
  // can badge it (removing bundle A won't drop it if bundle B still claims it).
  const loadedBundles = computed(() => {
    const bundles = []
    for (const tplId of templateLoadedFor.value) {
      const tpl = templates.find(t => t.id === tplId)
      if (!tpl) continue
      const steps = wizardSteps.value
        .map((s, idx) => ({ s, idx }))
        .filter(({ s }) => (s.source_templates || []).includes(tplId))
        .map(({ s, idx }) => ({
          idx,
          subject: s.subject,
          merge_key: s.merge_key,
          shared: (s.source_templates || []).length > 1,
        }))
      bundles.push({
        id: tpl.id,
        name: tpl.name,
        icon: tpl.icon,
        steps,
      })
    }
    return bundles
  })

  function loadTemplateFromItem (item) {
    if (!item.project_template_id) return
    const tpl = templates.find(t => t.id === item.project_template_id)
    if (!tpl || templateLoadedFor.value.has(item.project_template_id)) return

    const merged = mergeTemplateSteps(tpl)
    templateLoadedFor.value = new Set([...templateLoadedFor.value, item.project_template_id])
    if (merged > 0) {
      lastMergeCount.value = merged
      mergedTemplateLabels.value = [...mergedTemplateLabels.value, tpl.name]
    }
  }

  return {
    catalogProducts,
    catalogLoading,
    catalogFilter,
    catalogCategories: CATALOG_CATEGORIES,
    residentialPresets: RESIDENTIAL_PRESETS,
    internetHeroTiers: INTERNET_HERO_TIERS,
    tvHeroTiers: TV_HERO_TIERS,
    selectedHeroTier,
    selectHeroTier,
    selectedTvTier,
    selectTvTier,
    filteredCatalog,
    loadCatalog,
    addCatalogItem,
    matchCompetitor,
    clearCompetitor,
    applyPreset,
    loadTemplateFromItem,
    removeTemplate,
    toggleTemplate,
    removeStepFromBundle,
    addStepToBundle,
    loadedBundles,
    lastMergeCount,
    mergedTemplateLabels,
  }
}
