<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, onUnmounted, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { getCachedImageUrl, revokeAllImageUrls } from '../../common/imageCache'
import { isParentAuthenticated } from '../../stores/auth'
import { eventBus } from '@/common/eventBus'
import {
  maybeShow as tutorialMaybeShow,
  markStepSeen as tutorialMark,
  tutorialReady,
  isTutorialActive,
} from '@/tutorial/controller'
import type {
  Child,
  ChildModifiedEventPayload,
  ChildTaskTriggeredEventPayload,
  ChildRewardTriggeredEventPayload,
  Event,
} from '@/common/models'
import MessageBlock from '@/components/shared/MessageBlock.vue'
//import '@/assets/button-shared.css'

import FloatingActionButton from '../shared/FloatingActionButton.vue'
import DeleteModal from '../shared/DeleteModal.vue'

const router = useRouter()
const children = ref<Child[]>([])
const loading = ref(true)
const error = ref<string | null>(null)
const images = ref<Map<string, string>>(new Map()) // Store image URLs

const imageCacheName = 'images-v1'

// UI state for kebab menus & delete confirmation
const activeMenuFor = ref<string | number | null>(null) // which child card shows menu
const confirmDeleteVisible = ref(false)
const deletingChildId = ref<string | number | null>(null)
const deleting = ref(false)

const openChildEditor = (child: Child, evt?: Event) => {
  evt?.stopPropagation()
  router.push({ name: 'ChildEditView', params: { id: child.id } })
}

async function handleChildModified(event: Event) {
  const payload = event.payload as ChildModifiedEventPayload
  const childId = payload.child_id

  switch (payload.operation) {
    case 'DELETE':
      children.value = children.value.filter((c) => c.id !== childId)
      break

    case 'ADD':
      try {
        const list = await fetchChildren()
        children.value = list
      } catch (err) {
        console.warn('Failed to fetch children after ADD operation:', err)
      }
      break

    case 'EDIT':
      try {
        const list = await fetchChildren()
        const updatedChild = list.find((c) => c.id === childId)
        if (updatedChild) {
          const idx = children.value.findIndex((c) => c.id === childId)
          if (idx !== -1) {
            children.value[idx] = updatedChild
          } else {
            console.warn(`EDIT operation: child with id ${childId} not found in current list.`)
          }
        } else {
          console.warn(
            `EDIT operation: updated child with id ${childId} not found in fetched list.`,
          )
        }
      } catch (err) {
        console.warn('Failed to fetch children after EDIT operation:', err)
      }
      break

    default:
      console.warn(`Unknown operation: ${payload.operation}`)
  }
}

function handleChildTaskTriggered(event: Event) {
  const payload = event.payload as ChildTaskTriggeredEventPayload
  const childId = payload.child_id
  const child = children.value.find((c) => c.id === childId)
  if (child) {
    child.points = payload.points
  } else {
    console.warn(`Child with id ${childId} not found when updating points.`)
  }
}

function handleChildRewardTriggered(event: Event) {
  const payload = event.payload as ChildRewardTriggeredEventPayload
  const childId = payload.child_id
  const child = children.value.find((c) => c.id === childId)
  if (child) {
    child.points = payload.points
  } else {
    console.warn(`Child with id ${childId} not found when updating points.`)
  }
}

// points update state
const updatingPointsFor = ref<string | number | null>(null)

const fetchImage = async (imageId: string) => {
  try {
    const url = await getCachedImageUrl(imageId, imageCacheName)
    images.value.set(imageId, url)
  } catch (err) {
    console.warn('Failed to load child image', imageId, err)
  }
}

const fetchChildren = async (): Promise<Child[]> => {
  loading.value = true
  error.value = null
  images.value.clear()

  try {
    const response = await fetch('/api/child/list')
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    const data = await response.json()
    const childList = data.children || []

    // Fetch images for each child (shared cache util)
    await Promise.all(
      childList.map((child) => {
        if (child.image_id) {
          return fetchImage(child.image_id)
        }
        return Promise.resolve()
      }),
    )
    children.value = childList
    return childList
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Failed to fetch children'
    console.error('Error fetching children:', err)
    children.value = []
    return []
  } finally {
    loading.value = false
  }
}

const createChild = () => {
  router.push({ name: 'CreateChild' })
}

function maybeTriggerCreateChildTutorial() {
  if (!isParentAuthenticated.value) return
  if (loading.value) return
  if (!tutorialReady.value) return
  if (children.value.length === 0) {
    nextTick(() => {
      tutorialMaybeShow('create-child')
    })
  } else {
    void tutorialMark('has-created-child')
    nextTick(() => {
      tutorialMaybeShow('child-points')
    })
  }
}

watch([tutorialReady, loading, children, isParentAuthenticated], maybeTriggerCreateChildTutorial, {
  immediate: false,
  deep: true,
})

onMounted(async () => {
  eventBus.on('child_modified', handleChildModified)
  eventBus.on('child_task_triggered', handleChildTaskTriggered)
  eventBus.on('child_reward_triggered', handleChildRewardTriggered)

  const listPromise = fetchChildren()
  listPromise.then(() => {
    maybeTriggerCreateChildTutorial()
  })
  // listen for outside clicks to auto-close any open kebab menu
  document.addEventListener('click', onDocClick, true)
})

onUnmounted(() => {
  eventBus.off('child_modified', handleChildModified)
  eventBus.off('child_task_triggered', handleChildTaskTriggered)
  eventBus.off('child_reward_triggered', handleChildRewardTriggered)
})

const shouldIgnoreNextCardClick = ref(false)

const onDocClick = (e: MouseEvent) => {
  if (activeMenuFor.value !== null) {
    const path = (e.composedPath && e.composedPath()) || (e as any).path || []
    const clickedInsideKebab = path.some((node: unknown) => {
      if (!(node instanceof HTMLElement)) return false
      return (
        node.classList.contains('kebab-wrap') ||
        node.classList.contains('kebab-btn') ||
        node.classList.contains('kebab-menu')
      )
    })
    if (!clickedInsideKebab) {
      activeMenuFor.value = null
      // If the click was on a card, set the flag to ignore the next card click
      if (
        path.some((node: unknown) => node instanceof HTMLElement && node.classList.contains('card'))
      ) {
        shouldIgnoreNextCardClick.value = true
      }
    }
  }
}

const selectChild = (childId: string | number) => {
  if (shouldIgnoreNextCardClick.value) {
    shouldIgnoreNextCardClick.value = false
    return
  }
  if (activeMenuFor.value !== null) {
    // If kebab menu is open, ignore card clicks
    return
  }
  if (isParentAuthenticated.value) {
    router.push(`/parent/${childId}`)
  } else {
    router.push(`/child/${childId}`)
  }
}

// kebab menu helpers
const openMenu = (childId: string | number, evt?: Event) => {
  evt?.stopPropagation()
  activeMenuFor.value = childId
  tutorialMaybeShow(
    'child-kebab',
    () => document.querySelector('.kebab-menu') as HTMLElement | null,
  )
}
const closeMenu = () => {
  activeMenuFor.value = null
}

// delete flow
const askDelete = (childId: string | number, evt?: Event) => {
  evt?.stopPropagation()
  deletingChildId.value = childId
  confirmDeleteVisible.value = true
  closeMenu()
}

const performDelete = async () => {
  if (!deletingChildId.value) return
  deleting.value = true
  try {
    const resp = await fetch(`/api/child/${deletingChildId.value}`, {
      method: 'DELETE',
    })
    if (!resp.ok) {
      throw new Error(`Delete failed: ${resp.status}`)
    }
    // refresh list
    await fetchChildren()
  } catch (err) {
    console.error('Failed to delete child', deletingChildId.value, err)
  } finally {
    deleting.value = false
    confirmDeleteVisible.value = false
    deletingChildId.value = null
  }
}

// Delete Points flow: set points to 0 via API and refresh points display
const deletePoints = async (childId: string | number, evt?: Event) => {
  evt?.stopPropagation()
  closeMenu()
  updatingPointsFor.value = childId
  try {
    const resp = await fetch(`/api/child/${childId}/edit`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ points: 0 }),
    })
    if (!resp.ok) {
      throw new Error(`Failed to update points: ${resp.status}`)
    }
    // no need to refresh since we update optimistically via eventBus
  } catch (err) {
    console.error('Failed to delete points for child', childId, err)
  } finally {
    updatingPointsFor.value = null
  }
}

onBeforeUnmount(() => {
  document.removeEventListener('click', onDocClick, true)
  revokeAllImageUrls()
})
</script>

<template>
  <div>
    <MessageBlock v-if="children.length === 0" message="No children">
      <span v-if="!isParentAuthenticated">
        <button class="round-btn" @click="eventBus.emit('open-login')">Switch</button> to parent
        mode to create a child
      </span>
      <span v-else> <button class="round-btn" @click="createChild">Create</button> a child </span>
    </MessageBlock>

    <div v-else-if="loading" class="loading">Loading...</div>

    <div v-else-if="error" class="error">Error: {{ error }}</div>

    <div v-else class="grid">
      <div v-for="child in children" :key="child.id" class="card" @click="selectChild(child.id)">
        <!-- kebab menu shown only for authenticated parent -->
        <div v-if="isParentAuthenticated" class="kebab-wrap" @click.stop>
          <!-- kebab button -->
          <button
            class="kebab-btn"
            @mousedown.stop.prevent
            @click="openMenu(child.id, $event)"
            :aria-expanded="activeMenuFor === child.id ? 'true' : 'false'"
            aria-label="Options"
          >
            ⋮
          </button>

          <!-- menu items -->
          <div
            v-if="activeMenuFor === child.id"
            class="kebab-menu"
            @mousedown.stop.prevent
            @click.stop
          >
            <button
              class="menu-item"
              @mousedown.stop.prevent
              @click="openChildEditor(child, $event)"
            >
              Edit Child
            </button>
            <button
              class="menu-item"
              @mousedown.stop.prevent
              @click="deletePoints(child.id, $event)"
              :disabled="updatingPointsFor === child.id"
            >
              {{ updatingPointsFor === child.id ? 'Updating…' : 'Delete Points' }}
            </button>
            <button class="menu-item danger" @mousedown.stop.prevent @click="askDelete(child.id)">
              Delete Child
            </button>
          </div>
        </div>
        <div class="card-content">
          <h2>{{ child.name }}</h2>
          <img
            v-if="images.get(child.image_id)"
            :src="images.get(child.image_id)"
            alt="Child Image"
            class="child-image"
          />
          <p class="age">Age: {{ child.age }}</p>
          <p class="points">Points: {{ child.points ?? 0 }}</p>
        </div>
      </div>
    </div>

    <DeleteModal
      :show="confirmDeleteVisible"
      message="Are you sure you want to delete this child?"
      @confirm="performDelete"
      @cancel="confirmDeleteVisible = false"
    />

    <FloatingActionButton
      v-if="isParentAuthenticated"
      aria-label="Add Child"
      :disabled="isTutorialActive"
      @click="createChild"
    />
  </div>
</template>

<style scoped>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.2rem 1.2rem;
  justify-items: center;
}

.card {
  background: var(--card-bg);
  box-shadow: var(--card-shadow);
  border-radius: 12px;
  overflow: visible; /* allow menu to overflow */
  transition: all 0.3s ease;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  position: relative; /* for kebab positioning */
  width: 250px;
}

/* kebab button / menu (fixed-size button, absolutely positioned menu) */
.kebab-wrap {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 20;
  /* keep the wrapper only as a positioning context */
}

.kebab-btn {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  color: var(--kebab-icon-color);
  border-radius: 6px;
  box-sizing: border-box;
  font-size: 1.5rem;
}

/* consistent focus ring without changing layout */
.kebab-btn:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.18);
}

/* Menu overlays the card and does NOT alter flow */
.kebab-menu {
  position: absolute;
  top: 44px;
  right: 0;
  margin: 0;
  min-width: 150px;
  background: var(--kebab-menu-bg);
  border: 1.5px solid var(--kebab-menu-border);
  box-shadow: var(--kebab-menu-shadow);
  backdrop-filter: blur(var(--kebab-menu-blur));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 30;
}

.menu-item {
  padding: 1.1rem 0.9rem; /* Increase vertical padding for bigger touch area */
  background: transparent;
  border: 0;
  text-align: left;
  cursor: pointer;
  font-weight: 600;
  color: var(--menu-item-color);
  font-size: 1.1rem; /* Slightly larger text for readability */
}

.menu-item + .menu-item {
  margin-top: 0.5rem; /* Add space between menu items */
}

@media (max-width: 600px) {
  .menu-item {
    padding: 0.85rem 0.7rem;
    font-size: 1rem;
  }
  .menu-item + .menu-item {
    margin-top: 0.35rem;
  }
}

.menu-item:hover {
  background: var(--menu-item-hover-bg);
}

.menu-item.danger {
  color: var(--menu-item-danger);
}

/* card content */
.card-content {
  padding: 1.5rem;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.card h2 {
  font-size: 1.5rem;
  color: var(--card-title);
  margin-bottom: 0.5rem;
  word-break: break-word;
  text-align: center;
}

.age {
  font-size: 1.1rem;
  color: var(--age-color);
  font-weight: 500;
  text-align: center;
}

.child-image {
  width: 100px;
  height: 100px;
  object-fit: cover;
  border-radius: 50%;
  margin: 0 auto 1rem auto;
  background: var(--child-image-bg);
}

.points {
  font-size: 1.05rem;
  color: var(--points-color);
  margin-top: 0.4rem;
  font-weight: 600;
  text-align: center;
}

/* Loading, error, empty states */
.loading,
.empty {
  margin: 1.2rem 0;
  color: var(--list-loading-color);
  font-size: 1.15rem;
  font-weight: 600;
  text-align: center;
  line-height: 1.5;
}
.error {
  color: var(--error);
  margin-top: 0.7rem;
  text-align: center;
  background: var(--error-bg);
  border-radius: 8px;
  padding: 1rem;
}
.value {
  font-weight: 600;
  margin-left: 1rem;
}
</style>
