<template>
  <div class="chatter-panel">
    <div class="chatter-header">
      <span class="text-weight-bold" style="font-size:1.1rem">Convos</span>
      <q-space />
      <q-badge v-if="unreadCount" color="red" text-color="white" class="q-mr-xs">{{ unreadCount }}</q-badge>
      <q-btn flat dense round size="sm" icon="refresh" color="grey-6" @click="loadAll" :loading="loading" />
    </div>

    <ComposeBar
      v-model:channel="composeChannel" v-model:text="composeText"
      v-model:email-subject="emailSubject" v-model:sms-to="smsTo" v-model:call-to="callTo"
      :customer-phone="customerPhone" :phone-options="phoneOptions"
      :sending="sending" :calling="calling" :canned-responses="cannedResponses"
      @send="send" @call="initiateCall" @use-canned="useCanned" @manage-canned="cannedModal = true" />

    <!-- Bulk action bar -->
    <div v-if="selectedIds.size > 0" class="chatter-bulk-bar">
      <q-checkbox :model-value="selectedIds.size === filteredEntries.length" dense size="sm"
        @update:model-value="v => v ? selectAllEntries() : selectedIds.clear()" class="q-mr-xs" />
      <span class="text-caption text-weight-medium">{{ selectedIds.size }} sélectionné{{ selectedIds.size > 1 ? 's' : '' }}</span>
      <q-space />
      <q-btn flat dense size="sm" icon="delete_outline" color="red-5" :loading="bulkDeleting" @click="confirmBulkDelete">
        <q-tooltip>Supprimer la sélection</q-tooltip>
      </q-btn>
      <q-btn flat dense size="sm" icon="close" color="grey-6" @click="selectedIds.clear()" />
    </div>

    <div class="chatter-tabs">
      <q-btn-toggle v-model="activeTab" no-caps dense unelevated size="xs" spread
        toggle-color="indigo-6" color="grey-2" text-color="grey-7" :options="tabOptions" />
    </div>

    <div class="chatter-timeline" ref="timelineRef">
      <div v-if="loading && !entries.length" class="flex flex-center q-pa-lg">
        <q-spinner size="24px" color="indigo-5" />
      </div>

      <div v-else-if="!filteredEntries.length" class="text-center text-grey-5 q-pa-lg text-caption">
        {{ activeTab === 'all' ? 'Aucune communication' : 'Aucun ' + tabLabel }}
      </div>

      <template v-else>
        <template v-for="(group, idx) in groupedEntries" :key="idx">
          <div class="chatter-date-sep"><span>{{ group.label }}</span></div>
          <div v-for="entry in group.items" :key="entry.id" class="chatter-entry"
            :class="[...entryClass(entry), { 'entry-selected': selectedIds.has(entry.id) }]"
            @mouseenter="hoveredEntry = entry.id" @mouseleave="hoveredEntry = null">
            <div v-if="selectedIds.size > 0" class="entry-checkbox">
              <q-checkbox :model-value="selectedIds.has(entry.id)" dense size="sm"
                @update:model-value="toggleEntry(entry.id)" @click.stop />
            </div>
            <div v-else class="entry-icon" :class="'icon-' + entry.channel"
              @click.stop="startSelection(entry.id)">
              <q-icon :name="channelIcon(entry)" size="16px" />
            </div>
            <div class="entry-body">
              <div class="entry-header">
                <span class="entry-direction">
                  <q-icon :name="entry.direction === 'out' ? 'call_made' : entry.direction === 'in' ? 'call_received' : 'edit_note'"
                    size="12px" :color="entry.direction === 'out' ? 'indigo-4' : entry.direction === 'in' ? 'green-6' : 'grey-5'" />
                </span>
                <span class="entry-author text-weight-medium">{{ entry.author }}</span>
                <q-space />
                <div v-if="hoveredEntry === entry.id" class="entry-actions">
                  <q-btn flat dense round size="xs" icon="edit" color="grey-5" @click="startEdit(entry)" title="Modifier" />
                  <q-btn flat dense round size="xs" icon="delete_outline" color="red-4" @click="confirmDelete(entry)" title="Supprimer" />
                </div>
                <span class="entry-time text-caption text-grey-5">{{ formatTime(entry.date) }}</span>
              </div>

              <div v-if="editingEntry === entry.id" class="entry-edit">
                <q-input v-model="editText" dense outlined autogrow :input-style="{ fontSize: '0.82rem' }"
                  @keydown.escape="editingEntry = null"
                  @keydown.ctrl.enter="saveEdit(entry)" @keydown.meta.enter="saveEdit(entry)" />
                <div class="row q-gutter-xs q-mt-xs">
                  <q-btn dense unelevated size="xs" color="indigo-6" label="Sauver" @click="saveEdit(entry)" :loading="savingEdit" />
                  <q-btn dense flat size="xs" color="grey-6" label="Annuler" @click="editingEntry = null" />
                </div>
              </div>

              <div v-else class="entry-content" :class="{ 'entry-note': entry.channel === 'note' }">{{ entry.text }}</div>

              <div v-if="entry.channel === 'phone' && entry.meta" class="entry-meta text-caption text-grey-5">
                <q-icon name="timer" size="12px" class="q-mr-xs" />
                {{ entry.meta.duration || '0s' }}
                <template v-if="entry.meta.status"> &middot; {{ entry.meta.status }}</template>
              </div>
              <div v-if="entry.linkedTo && entry.linkedTo !== customerName" class="entry-link">
                <q-chip dense size="sm" icon="link" clickable color="grey-2" text-color="grey-8"
                  @click="$emit('navigate', entry.linkedDoctype, entry.linkedTo)">
                  {{ entry.linkedDoctype }}: {{ entry.linkedTo }}
                </q-chip>
              </div>
            </div>
          </div>
        </template>
      </template>
    </div>

    <q-dialog v-model="deleteDialog">
      <q-card style="min-width:300px">
        <q-card-section>
          <div class="text-weight-bold">Supprimer ce message ?</div>
          <div class="text-caption text-grey-6 q-mt-xs">Cette action est irréversible.</div>
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat label="Annuler" color="grey-7" v-close-popup />
          <q-btn unelevated label="Supprimer" color="red" :loading="deleting" @click="doDelete" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <q-dialog v-model="bulkDeleteDialog">
      <q-card style="min-width:300px">
        <q-card-section>
          <div class="text-weight-bold">Supprimer {{ selectedIds.size }} message{{ selectedIds.size > 1 ? 's' : '' }} ?</div>
          <div class="text-caption text-grey-6 q-mt-xs">Cette action est irréversible.</div>
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat label="Annuler" color="grey-7" v-close-popup />
          <q-btn unelevated label="Supprimer tout" color="red" :loading="bulkDeleting" @click="doBulkDelete" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <PhoneModal v-model="phoneModalOpen" :initial-number="callTo || smsTo || customerPhone"
      :customer-name="props.customerName" :customer-erp-name="props.customerName"
      :provider="phoneProvider" @call-ended="onCallEnded" />

    <q-dialog v-model="cannedModal">
      <q-card style="min-width:460px;max-width:600px">
        <q-card-section>
          <div class="text-weight-bold" style="font-size:1rem">Réponses rapides</div>
          <div class="text-caption text-grey-6">Tapez <code>::raccourci</code> dans la zone de texte pour insérer une réponse rapide.</div>
        </q-card-section>
        <q-separator />
        <q-card-section style="max-height:350px;overflow-y:auto;padding:8px 16px">
          <div v-for="(cr, i) in cannedResponses" :key="i" class="canned-row">
            <div class="canned-shortcut">
              <q-input v-model="cr.shortcut" dense borderless prefix="::" :input-style="{ fontSize:'0.82rem', fontWeight:600 }" style="width:100px" />
            </div>
            <div class="canned-text">
              <q-input v-model="cr.text" dense borderless autogrow :input-style="{ fontSize:'0.82rem' }" placeholder="Texte de la réponse..." style="flex:1" />
            </div>
            <q-btn flat dense round size="xs" icon="delete_outline" color="red-4" @click="removeCanned(i)" />
          </div>
          <div v-if="!cannedResponses.length" class="text-grey-5 text-caption text-center q-py-md">
            Aucune réponse rapide. Cliquez + pour en ajouter.
          </div>
        </q-card-section>
        <q-separator />
        <q-card-actions>
          <q-btn flat dense icon="add" label="Ajouter" color="indigo-6" @click="addCanned" />
          <q-space />
          <q-btn flat label="Fermer" color="grey-7" v-close-popup />
          <q-btn unelevated label="Sauver" color="indigo-6" @click="saveCanned" v-close-popup />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, watch, nextTick, onUnmounted } from 'vue'
import { Notify } from 'quasar'
import { listDocs, createDoc, updateDoc, deleteDoc } from 'src/api/erp'
import { useSSE, sendSmsViaHub } from 'src/composables/useSSE'
import ComposeBar from './ComposeBar.vue'
import PhoneModal from './PhoneModal.vue'

const TAB_OPTIONS = [
  { label: 'Tout', value: 'all', icon: 'forum' },
  { label: 'SMS', value: 'sms', icon: 'sms' },
  { label: 'Appels', value: 'phone', icon: 'phone' },
  { label: 'Email', value: 'email', icon: 'email' },
  { label: 'Notes', value: 'note', icon: 'sticky_note_2' },
]
const TAB_LABEL_MAP = { sms: 'SMS', phone: 'appel', email: 'email', note: 'note' }
const CHANNEL_ICONS = { sms: 'sms', phone: 'phone', email: 'email', note: 'sticky_note_2', other: 'chat' }

const props = defineProps({
  customerName: { type: String, required: true },
  customerPhone: { type: String, default: '' },
  customerPhones: { type: Array, default: () => [] },
  customerEmail: { type: String, default: '' },
  comments: { type: Array, default: () => [] },
})

const emit = defineEmits(['navigate', 'note-added', 'note-updated'])

const loading = ref(false)
const activeTab = ref('all')
const composeChannel = ref('sms')
const composeText = ref('')
const emailSubject = ref('')
const sending = ref(false)
const calling = ref(false)
const smsTo = ref('')
const callTo = ref('')
const timelineRef = ref(null)
const communications = ref([])
const unreadCount = ref(0)
const hoveredEntry = ref(null)
const editingEntry = ref(null)
const editText = ref('')
const savingEdit = ref(false)
const deleteDialog = ref(false)
const deleteTarget = ref(null)
const deleting = ref(false)
const phoneModalOpen = ref(false)
const phoneProvider = ref('twilio')
const cannedModal = ref(false)
const cannedResponses = ref(loadCannedResponses())
const selectedIds = ref(new Set())
const bulkDeleteDialog = ref(false)
const bulkDeleting = ref(false)

function loadCannedResponses () {
  try { return JSON.parse(localStorage.getItem('ops-canned-responses') || '[]') } catch { return [] }
}
function addCanned () { cannedResponses.value.push({ shortcut: '', text: '' }) }
function removeCanned (i) { cannedResponses.value.splice(i, 1) }
function saveCanned () {
  const valid = cannedResponses.value.filter(c => c.shortcut.trim() && c.text.trim())
  cannedResponses.value = valid
  localStorage.setItem('ops-canned-responses', JSON.stringify(valid))
  Notify.create({ type: 'positive', message: 'Réponses rapides sauvegardées', timeout: 2000 })
}
function useCanned (cr) { composeText.value = cr.text }

const tabOptions = computed(() => TAB_OPTIONS)
const tabLabel = computed(() => TAB_LABEL_MAP[activeTab.value] || 'message')

const phoneOptions = computed(() => {
  if (props.customerPhones.length) return props.customerPhones
  return props.customerPhone
    ? [{ label: props.customerPhone, value: props.customerPhone }]
    : [{ label: 'Aucun numero', value: '', disable: true }]
})

watch(() => props.customerPhone, v => {
  if (v && !smsTo.value) smsTo.value = v
  if (v && !callTo.value) callTo.value = v
}, { immediate: true })

const { connect: sseConnect, disconnect: sseDisconnect, connected: sseConnected } = useSSE({
  onMessage: async (data) => {
    if (data.customer === props.customerName) {
      await loadCommunications()
      if (data.direction === 'in') {
        playNotificationSound()
        Notify.create({
          type: 'info', icon: 'sms',
          message: `${data.type === 'sms' ? 'SMS' : 'Message'} de ${data.customer_name || data.phone || 'Client'}`,
          timeout: 3000, position: 'top-right',
        })
      }
    }
  },
})

let fallbackTimer = null
function startFallbackPoll () {
  stopFallbackPoll()
  fallbackTimer = setInterval(() => loadCommunications(), sseConnected.value ? 30000 : 5000)
}
function stopFallbackPoll () { if (fallbackTimer) { clearInterval(fallbackTimer); fallbackTimer = null } }

async function loadCommunications () {
  try {
    const data = await listDocs('Communication', {
      filters: { reference_doctype: 'Customer', reference_name: props.customerName },
      fields: ['name', 'subject', 'content', 'text_content', 'communication_medium',
        'sent_or_received', 'communication_date', 'creation', 'phone_no',
        'sender', 'sender_full_name', 'status', 'delivery_status',
        'reference_doctype', 'reference_name', 'message_id', 'communication_type'],
      limit: 100, orderBy: 'communication_date asc',
    })
    communications.value = data
    unreadCount.value = data.filter(m => m.sent_or_received === 'Received' && m.status === 'Open').length
  } catch {}
}

function playNotificationSound () {
  try {
    const ctx = new (window.AudioContext || window.webkitAudioContext)()
    const osc = ctx.createOscillator()
    const gain = ctx.createGain()
    osc.connect(gain); gain.connect(ctx.destination)
    osc.type = 'sine'
    osc.frequency.setValueAtTime(880, ctx.currentTime)
    osc.frequency.setValueAtTime(1100, ctx.currentTime + 0.1)
    gain.gain.setValueAtTime(0.15, ctx.currentTime)
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3)
    osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.3)
  } catch {}
}

async function loadAll () {
  loading.value = true
  await loadCommunications()
  loading.value = false
  sseConnect(['customer:' + props.customerName])
  startFallbackPoll()
}

const entries = computed(() => {
  const items = []
  for (const c of communications.value) {
    const medium = (c.communication_medium || '').toLowerCase()
    const channel = medium === 'sms' ? 'sms' : medium === 'phone' ? 'phone' : medium === 'email' ? 'email' : 'other'
    items.push({
      id: c.name, channel,
      direction: c.sent_or_received === 'Sent' ? 'out' : 'in',
      author: c.sent_or_received === 'Sent' ? (c.sender_full_name || 'Targo Ops') : (c.sender_full_name || c.phone_no || c.sender || 'Client'),
      text: stripHtml(c.content || c.text_content || c.subject || ''),
      date: c.communication_date || c.creation,
      status: c.status, deliveryStatus: c.delivery_status,
      linkedDoctype: c.reference_doctype, linkedTo: c.reference_name,
      meta: channel === 'phone' ? { duration: null, status: c.delivery_status || null } : null,
      raw: c, doctype: 'Communication',
    })
  }
  for (const n of (props.comments || [])) {
    items.push({
      id: 'note-' + n.name, channel: 'note', direction: 'internal',
      author: n.comment_by?.split('@')[0] || 'Admin',
      text: stripHtml(n.content || ''),
      date: n.creation, status: null, linkedDoctype: null, linkedTo: null, meta: null,
      raw: n, doctype: 'Comment', docName: n.name,
    })
  }
  return items.sort((a, b) => new Date(b.date) - new Date(a.date))
})

const filteredEntries = computed(() => activeTab.value === 'all' ? entries.value : entries.value.filter(e => e.channel === activeTab.value))

const groupedEntries = computed(() => {
  const groups = []
  let currentLabel = ''
  for (const entry of filteredEntries.value) {
    const label = dateLabel(entry.date)
    if (label !== currentLabel) { currentLabel = label; groups.push({ label, items: [] }) }
    groups[groups.length - 1].items.push(entry)
  }
  return groups
})

function startEdit (entry) { editingEntry.value = entry.id; editText.value = entry.text }

async function saveEdit (entry) {
  if (!editText.value.trim()) return
  savingEdit.value = true
  try {
    const doctype = entry.doctype || (entry.channel === 'note' ? 'Comment' : 'Communication')
    const name = entry.docName || entry.raw?.name
    if (!name) return
    await updateDoc(doctype, name, { content: editText.value.trim() })
    entry.raw.content = editText.value.trim()
    editingEntry.value = null
    doctype === 'Comment' ? emit('note-updated') : await loadCommunications()
    Notify.create({ type: 'positive', message: 'Message modifié', timeout: 2000 })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 3000 })
  } finally { savingEdit.value = false }
}

function confirmDelete (entry) { deleteTarget.value = entry; deleteDialog.value = true }

async function doDelete () {
  if (!deleteTarget.value) return
  const entry = deleteTarget.value
  const doctype = entry.doctype || (entry.channel === 'note' ? 'Comment' : 'Communication')
  const name = entry.docName || entry.raw?.name
  if (!name) return

  // Optimistic removal
  if (doctype === 'Comment') {
    const idx = props.comments.findIndex(n => n.name === entry.raw?.name)
    if (idx !== -1) props.comments.splice(idx, 1)
  } else {
    const idx = communications.value.findIndex(c => c.name === name)
    if (idx !== -1) communications.value.splice(idx, 1)
  }
  deleteDialog.value = false; deleteTarget.value = null
  Notify.create({ type: 'positive', message: 'Message supprimé', timeout: 2000 })

  try {
    await deleteDoc(doctype, name)
    if (doctype === 'Comment') emit('note-updated')
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur suppression: ' + e.message, timeout: 3000 })
    await loadCommunications()
    if (doctype === 'Comment') emit('note-updated')
  }
}

// ── Bulk selection ──
function toggleEntry (id) {
  const s = new Set(selectedIds.value)
  s.has(id) ? s.delete(id) : s.add(id)
  selectedIds.value = s
}
function startSelection (id) {
  selectedIds.value = new Set([id])
}
function selectAllEntries () {
  selectedIds.value = new Set(filteredEntries.value.map(e => e.id))
}
function confirmBulkDelete () { bulkDeleteDialog.value = true }

async function doBulkDelete () {
  bulkDeleting.value = true
  const toDelete = entries.value.filter(e => selectedIds.value.has(e.id))
  let ok = 0, fail = 0
  for (const entry of toDelete) {
    const doctype = entry.doctype || (entry.channel === 'note' ? 'Comment' : 'Communication')
    const name = entry.docName || entry.raw?.name
    if (!name) continue
    try {
      await deleteDoc(doctype, name)
      ok++
    } catch { fail++ }
  }
  // Remove deleted from local state
  for (const entry of toDelete) {
    if (entry.doctype === 'Comment' || entry.channel === 'note') {
      const idx = props.comments.findIndex(n => n.name === entry.raw?.name)
      if (idx !== -1) props.comments.splice(idx, 1)
    } else {
      const idx = communications.value.findIndex(c => c.name === (entry.docName || entry.raw?.name))
      if (idx !== -1) communications.value.splice(idx, 1)
    }
  }
  selectedIds.value = new Set()
  bulkDeleteDialog.value = false
  bulkDeleting.value = false
  Notify.create({ type: ok > 0 ? 'positive' : 'negative', message: `${ok} message${ok > 1 ? 's' : ''} supprimé${ok > 1 ? 's' : ''}${fail ? `, ${fail} erreur${fail > 1 ? 's' : ''}` : ''}`, timeout: 3000 })
  if (toDelete.some(e => e.channel === 'note')) emit('note-updated')
}

// Clear selection when switching tabs
watch(activeTab, () => { selectedIds.value = new Set() })

async function send () {
  if (!composeText.value.trim() || sending.value) return
  sending.value = true
  try {
    if (composeChannel.value === 'sms') await sendSms()
    else if (composeChannel.value === 'note') await addNote()
    else if (composeChannel.value === 'email') await sendEmail()
    composeText.value = ''; emailSubject.value = ''
    await loadCommunications()
    setTimeout(loadCommunications, 2000)
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message, timeout: 4000 })
  } finally { sending.value = false }
}

async function sendSms () {
  if (!smsTo.value) throw new Error('Aucun numero')
  await sendSmsViaHub(smsTo.value, composeText.value.trim(), props.customerName)
  Notify.create({ type: 'positive', message: 'SMS envoyé', timeout: 2000 })
}

async function addNote () {
  const doc = await createDoc('Comment', {
    comment_type: 'Comment', reference_doctype: 'Customer',
    reference_name: props.customerName, content: composeText.value.trim(),
  })
  emit('note-added', doc)
  Notify.create({ type: 'positive', message: 'Note ajoutée', timeout: 2000 })
}

async function sendEmail () {
  const { authFetch } = await import('src/api/auth')
  const { BASE_URL } = await import('src/config/erpnext')
  const res = await authFetch(BASE_URL + '/api/method/send_email_notification', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: props.customerEmail, subject: emailSubject.value.trim(), message: composeText.value.trim(), customer: props.customerName }),
  })
  if (!res.ok) throw new Error('Email failed')
  Notify.create({ type: 'positive', message: 'Email envoyé', timeout: 2000 })
}

async function initiateCall (provider = 'twilio') {
  const phone = callTo.value || smsTo.value || props.customerPhone
  if (!phone) { Notify.create({ type: 'warning', message: 'Aucun numéro', timeout: 3000 }); return }
  phoneProvider.value = provider; phoneModalOpen.value = true
}

async function onCallEnded (callInfo) {
  const phone = callInfo.remote || callTo.value || smsTo.value || props.customerPhone
  const durationStr = `${Math.floor(callInfo.duration / 60)}m${String(callInfo.duration % 60).padStart(2, '0')}s`
  try {
    await createDoc('Communication', {
      subject: (callInfo.direction === 'in' ? 'Appel de ' : 'Appel vers ') + phone,
      communication_type: 'Communication', communication_medium: 'Phone',
      sent_or_received: callInfo.direction === 'in' ? 'Received' : 'Sent',
      status: 'Linked', phone_no: phone, sender: 'sms@gigafibre.ca',
      sender_full_name: callInfo.direction === 'in' ? (callInfo.remoteName || phone) : 'Targo Ops',
      content: `Appel ${callInfo.direction === 'in' ? 'recu de' : 'vers'} ${phone} — Duree: ${durationStr}`,
      reference_doctype: 'Customer', reference_name: props.customerName,
    })
    await loadCommunications()
  } catch {}
}

function channelIcon (entry) { return CHANNEL_ICONS[entry.channel] || 'chat' }

function entryClass (entry) {
  return [
    'entry-' + entry.channel,
    entry.direction === 'in' ? 'entry-inbound' : entry.direction === 'out' ? 'entry-outbound' : 'entry-internal',
    entry.status === 'Open' ? 'entry-unread' : '',
  ]
}

function stripHtml (html) {
  if (!html) return ''
  const div = document.createElement('div'); div.innerHTML = html
  return div.textContent || div.innerText || ''
}

function formatTime (dt) {
  return dt ? new Date(dt).toLocaleTimeString('fr-CA', { hour: '2-digit', minute: '2-digit' }) : ''
}

function dateLabel (dt) {
  if (!dt) return ''
  const d = new Date(dt), now = new Date()
  if (d.toDateString() === now.toDateString()) return "Aujourd'hui"
  const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1)
  if (d.toDateString() === yesterday.toDateString()) return 'Hier'
  return d.toLocaleDateString('fr-CA', { weekday: 'short', month: 'short', day: 'numeric' })
}

onMounted(loadAll)
watch(() => props.customerName, loadAll)
onUnmounted(() => { sseDisconnect(); stopFallbackPoll() })
</script>

<style src="./chatter-panel.scss" scoped></style>
