import type { CalendarEventData } from '@/components/Calendar/CalendarEvent';
import { getSigner, ndk } from '@/ndk';
import { NDKEvent, type NDKKind } from '@nostr-dev-kit/ndk';
import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';

import '@components/Calendar/CalendarEvent';
import '@components/Calendar/CalendarEventDetailsDialog';
import '@components/Calendar/CalendarEventDialog';
import '@components/Calendar/CalendarHeader';
import '@components/Calendar/CalendarMonthView';
import '@components/Calendar/CalendarWeekView';

type CalendarView = 'month' | 'week';

interface NostrEvent {
  id: string;
  pubkey: string;
  created_at: number | undefined;
  kind: number;
  content: string;
  tags: string[][];
}

@customElement('arx-calendar-route')
export default class CalendarRoute extends LitElement {
  @state()
  private currentDate = new Date();

  @state()
  private selectedDate: Date | null = null;

  @state()
  private view: CalendarView = 'month';

  @state()
  private locale = 'en-US';

  @state()
  private events: CalendarEventData[] = [];

  @state()
  private showAddEventDialog = false;

  @state()
  private selectedEvent: CalendarEventData | null = null;

  override connectedCallback() {
    super.connectedCallback();
    this.loadLocale();
    this.loadEvents();
  }

  private loadLocale() {
    try {
      const options = localStorage.getItem('dateTimeFormatOptions');
      if (options) {
        const { locale } = JSON.parse(options);
        if (locale) {
          this.locale = locale;
        }
      }
    } catch {
      // Do nothing
    }
  }

  private async loadEvents() {
    await getSigner();
    try {
      const dateEvents = await ndk.fetchEvents({
        kinds: [31922 as NDKKind],
      });
      const timeEvents = await ndk.fetchEvents({
        kinds: [31923 as NDKKind],
      });

      const events = [
        ...Array.from(dateEvents).map((event) => this.parseDateEvent(event as unknown as NostrEvent)),
        ...Array.from(timeEvents).map((event) => this.parseTimeEvent(event as unknown as NostrEvent)),
      ];

      this.events = events;
    } catch (error) {
      console.error('Error loading events:', error);
    }
  }

  private parseDateEvent(event: NostrEvent): CalendarEventData {
    const startTag = event.tags.find((t) => t[0] === 'start');
    const titleTag = event.tags.find((t) => t[0] === 'title');
    if (!startTag || !titleTag) {
      throw new Error('Invalid event: missing required tags');
    }

    const start = new Date(startTag[1]);
    const endTag = event.tags.find((t) => t[0] === 'end');
    const end = endTag ? new Date(endTag[1]) : undefined;

    return {
      id: event.id,
      title: titleTag[1],
      start,
      end,
      description: event.content,
      location: event.tags.find((t) => t[0] === 'location')?.[1],
      participants: event.tags.filter((t) => t[0] === 'p').map((t) => t[1]),
      tags: event.tags.filter((t) => t[0] === 't').map((t) => t[1]),
      type: 'date',
    };
  }

  private parseTimeEvent(event: NostrEvent): CalendarEventData {
    const startTag = event.tags.find((t) => t[0] === 'start');
    const titleTag = event.tags.find((t) => t[0] === 'title');
    if (!startTag || !titleTag) {
      throw new Error('Invalid event: missing required tags');
    }

    const start = new Date(Number.parseInt(startTag[1]) * 1000);
    const endTag = event.tags.find((t) => t[0] === 'end');
    const end = endTag ? new Date(Number.parseInt(endTag[1]) * 1000) : undefined;

    return {
      id: event.id,
      title: titleTag[1],
      start,
      end,
      description: event.content,
      location: event.tags.find((t) => t[0] === 'location')?.[1],
      participants: event.tags.filter((t) => t[0] === 'p').map((t) => t[1]),
      tags: event.tags.filter((t) => t[0] === 't').map((t) => t[1]),
      type: 'time',
    };
  }

  private handlePrev() {
    if (this.view === 'month') {
      const newDate = new Date(this.currentDate);
      newDate.setMonth(newDate.getMonth() - 1);
      if (newDate.getMonth() === this.currentDate.getMonth()) newDate.setDate(0);
      this.currentDate = newDate;
    } else {
      const newDate = new Date(this.currentDate);
      newDate.setDate(newDate.getDate() - 7);
      this.currentDate = newDate;
    }
  }

  private handleNext() {
    if (this.view === 'month') {
      const newDate = new Date(this.currentDate);
      newDate.setMonth(newDate.getMonth() + 1);
      if (newDate.getMonth() === this.currentDate.getMonth()) newDate.setDate(32);
      this.currentDate = newDate;
    } else {
      const newDate = new Date(this.currentDate);
      newDate.setDate(newDate.getDate() + 7);
      this.currentDate = newDate;
    }
  }

  private handleToday() {
    this.currentDate = new Date();
  }

  private handleViewChange(e: CustomEvent) {
    this.view = e.detail.view;
  }

  private handleAddEvent() {
    this.showAddEventDialog = true;
  }

  private handleCloseDialog() {
    this.showAddEventDialog = false;
  }

  private handleDateClick(e: CustomEvent) {
    this.selectedDate = e.detail.date;
  }

  private handleEventClick(e: CustomEvent) {
    this.selectedEvent = e.detail.event;
  }

  private handleCloseEventDetails() {
    this.selectedEvent = null;
  }

  private async handleSubmit(e: CustomEvent) {
    await getSigner();
    try {
      const event = new NDKEvent(ndk);
      const { allDay, ...newEvent } = e.detail.event;
      event.kind = allDay ? 31922 : 31923;
      event.content = newEvent.description;

      const startDate = new Date(newEvent.startDate);
      if (allDay) {
        startDate.setHours(0, 0, 0, 0);
      } else if (newEvent.startTime) {
        const [hours, minutes] = newEvent.startTime.split(':');
        startDate.setHours(Number.parseInt(hours), Number.parseInt(minutes));
      }

      const tags = [
        ['title', newEvent.title],
        ['start', allDay ? startDate.toISOString().split('T')[0] : Math.floor(startDate.getTime() / 1000).toString()],
      ];

      if (newEvent.endDate) {
        const endDate = new Date(newEvent.endDate);
        if (allDay) {
          endDate.setHours(0, 0, 0, 0);
        } else if (newEvent.endTime) {
          const [hours, minutes] = newEvent.endTime.split(':');
          endDate.setHours(Number.parseInt(hours), Number.parseInt(minutes));
        }
        tags.push([
          'end',
          allDay ? endDate.toISOString().split('T')[0] : Math.floor(endDate.getTime() / 1000).toString(),
        ]);
      }

      if (newEvent.location) {
        tags.push(['location', newEvent.location]);
      }

      for (const participant of newEvent.participants) {
        tags.push(['p', participant]);
      }

      for (const tag of newEvent.tags) {
        tags.push(['t', tag]);
      }

      event.tags = tags;
      await event.sign();
      await event.publish();

      this.handleCloseDialog();
      await this.loadEvents();
    } catch (error) {
      console.error('Error creating event:', error);
    }
  }

  private formatDateRange(): string {
    if (this.view === 'month') {
      return this.currentDate.toLocaleDateString(this.locale, { month: 'long', year: 'numeric' });
    }

    const weekDays = this.getWeekDays();
    const firstDay = weekDays[0];
    const lastDay = weekDays[6];

    if (firstDay.getMonth() === lastDay.getMonth())
      return `${firstDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })} - ${lastDay.toLocaleDateString(this.locale, { day: 'numeric' })}, ${firstDay.getFullYear()}`;

    return `${firstDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })} - ${lastDay.toLocaleDateString(this.locale, { month: 'long', day: 'numeric' })}, ${firstDay.getFullYear()}`;
  }

  private getWeekDays(): Date[] {
    const days: Date[] = [];
    const currentDay = this.currentDate.getDay();
    const startOfWeek = new Date(this.currentDate);
    startOfWeek.setDate(this.currentDate.getDate() - currentDay);

    for (let i = 0; i < 7; i++) {
      const date = new Date(startOfWeek);
      date.setDate(startOfWeek.getDate() + i);
      days.push(date);
    }

    return days;
  }

  override render() {
    return html`
      <arx-calendar-header
        .view=${this.view}
        .currentDate=${this.currentDate}
        .isToday=${this.currentDate.toDateString() === new Date().toDateString()}
        .dateRange=${this.formatDateRange()}
        @prev=${this.handlePrev}
        @next=${this.handleNext}
        @today=${this.handleToday}
        @add-event=${this.handleAddEvent}
        @view-change=${this.handleViewChange}
      ></arx-calendar-header>

      ${when(
        this.view === 'month',
        () => html`
          <arx-calendar-month-view
            .currentDate=${this.currentDate}
            .selectedDate=${this.selectedDate}
            .events=${this.events}
            .locale=${this.locale}
            @date-click=${this.handleDateClick}
            @event-click=${this.handleEventClick}
          ></arx-calendar-month-view>
        `,
        () => html`
          <arx-calendar-week-view
            .currentDate=${this.currentDate}
            .events=${this.events}
            .locale=${this.locale}
            @event-click=${this.handleEventClick}
          ></arx-calendar-week-view>
        `,
      )}

      <arx-calendar-event-dialog
        .open=${this.showAddEventDialog}
        @close=${this.handleCloseDialog}
        @submit=${this.handleSubmit}
      ></arx-calendar-event-dialog>

      <arx-calendar-event-details-dialog
        .event=${this.selectedEvent}
        .open=${!!this.selectedEvent}
        .locale=${this.locale}
        @close=${this.handleCloseEventDetails}
      ></arx-calendar-event-details-dialog>
    `;
  }
}
