import { FolkShape } from "./folk-shape";
import { css, html } from "./tags";

const styles = css`
	:host {
		background: var(--rs-bg-surface, #fff);
		color: var(--rs-text-primary, #1e293b);
		border-radius: 8px;
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
		min-width: 280px;
		min-height: 320px;
	}

	.header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 8px 12px;
		background: #8b5cf6;
		color: white;
		border-radius: 8px 8px 0 0;
		font-size: 12px;
		font-weight: 600;
		cursor: move;
	}

	.header-title {
		display: flex;
		align-items: center;
		gap: 6px;
	}

	.header-actions {
		display: flex;
		gap: 4px;
	}

	.header-actions button {
		background: transparent;
		border: none;
		color: white;
		cursor: pointer;
		padding: 2px 6px;
		border-radius: 4px;
		font-size: 14px;
	}

	.header-actions button:hover {
		background: rgba(255, 255, 255, 0.2);
	}

	.calendar-container {
		padding: 12px;
	}

	.calendar-nav {
		display: flex;
		align-items: center;
		justify-content: space-between;
		margin-bottom: 12px;
	}

	.calendar-nav button {
		background: var(--rs-bg-surface-raised, #334155);
		border: none;
		border-radius: 4px;
		padding: 4px 8px;
		cursor: pointer;
		font-size: 16px;
		color: var(--rs-text-primary, #e2e8f0);
	}

	.calendar-nav button:hover {
		background: var(--rs-bg-surface-sunken, #0f172a);
	}

	.month-year {
		font-weight: 600;
		font-size: 14px;
		color: var(--rs-text-primary, #e2e8f0);
	}

	.weekdays {
		display: grid;
		grid-template-columns: repeat(7, 1fr);
		gap: 2px;
		margin-bottom: 4px;
	}

	.weekday {
		text-align: center;
		font-size: 11px;
		font-weight: 600;
		color: var(--rs-text-muted, #64748b);
		padding: 4px;
	}

	.days {
		display: grid;
		grid-template-columns: repeat(7, 1fr);
		gap: 2px;
	}

	.day {
		aspect-ratio: 1;
		display: flex;
		align-items: center;
		justify-content: center;
		font-size: 12px;
		border-radius: 4px;
		cursor: pointer;
		color: var(--rs-text-primary, #e2e8f0);
	}

	.day:hover {
		background: var(--rs-bg-hover, rgba(255, 255, 255, 0.05));
	}

	.day.other-month {
		color: var(--rs-text-muted, #64748b);
	}

	.day.today {
		background: #8b5cf6;
		color: white;
		font-weight: 600;
	}

	.day.selected {
		background: #ddd6fe;
		color: #5b21b6;
		font-weight: 600;
	}

	.day.has-event {
		position: relative;
	}

	.day.has-event::after {
		content: "";
		position: absolute;
		bottom: 2px;
		width: 4px;
		height: 4px;
		background: #8b5cf6;
		border-radius: 50%;
	}

	.day.has-event.today::after {
		background: white;
	}

	.events-list {
		margin-top: 12px;
		padding-top: 12px;
		border-top: 1px solid var(--rs-border, rgba(255, 255, 255, 0.1));
		max-height: 120px;
		overflow-y: auto;
	}

	.event-item {
		display: flex;
		align-items: center;
		gap: 8px;
		padding: 4px 8px;
		font-size: 12px;
		border-radius: 4px;
		margin-bottom: 4px;
		background: var(--rs-bg-surface-sunken, #0f172a);
	}

	.event-dot {
		width: 6px;
		height: 6px;
		border-radius: 50%;
		background: #8b5cf6;
	}
`;

export interface CalendarEvent {
	id: string;
	title: string;
	date: Date;
	color?: string;
}

declare global {
	interface HTMLElementTagNameMap {
		"folk-calendar": FolkCalendar;
	}
}

export class FolkCalendar extends FolkShape {
	static override tagName = "folk-calendar";

	static override portDescriptors = [
		{ name: "date", type: "string" as const, direction: "output" as const },
	];

	static {
		const sheet = new CSSStyleSheet();
		const parentRules = Array.from(FolkShape.styles.cssRules)
			.map((r) => r.cssText)
			.join("\n");
		const childRules = Array.from(styles.cssRules)
			.map((r) => r.cssText)
			.join("\n");
		sheet.replaceSync(`${parentRules}\n${childRules}`);
		this.styles = sheet;
	}

	#currentMonth = new Date();
	#selectedDate: Date | null = null;
	#events: CalendarEvent[] = [];
	#daysContainer: HTMLElement | null = null;
	#monthYearEl: HTMLElement | null = null;
	#eventsListEl: HTMLElement | null = null;

	get selectedDate() {
		return this.#selectedDate;
	}

	set selectedDate(date: Date | null) {
		this.#selectedDate = date;
		this.#render();
		this.dispatchEvent(new CustomEvent("date-select", { detail: { date } }));
	}

	get events() {
		return this.#events;
	}

	set events(events: CalendarEvent[]) {
		this.#events = events;
		this.#render();
	}

	addEvent(event: CalendarEvent) {
		this.#events.push(event);
		this.#render();
		this.dispatchEvent(new CustomEvent("event-add", { detail: { event } }));
	}

	override createRenderRoot() {
		const root = super.createRenderRoot();

		const wrapper = document.createElement("div");
		wrapper.innerHTML = html`
			<div class="header">
				<span class="header-title">
					<span>📅</span>
					<span>Calendar</span>
				</span>
				<div class="header-actions">
					<button class="close-btn" title="Close">×</button>
				</div>
			</div>
			<div class="calendar-container">
				<div class="calendar-nav">
					<button class="prev-btn">❮</button>
					<span class="month-year"></span>
					<button class="next-btn">❯</button>
				</div>
				<div class="weekdays">
					<span class="weekday">Sun</span>
					<span class="weekday">Mon</span>
					<span class="weekday">Tue</span>
					<span class="weekday">Wed</span>
					<span class="weekday">Thu</span>
					<span class="weekday">Fri</span>
					<span class="weekday">Sat</span>
				</div>
				<div class="days"></div>
				<div class="events-list"></div>
			</div>
		`;

		// Replace the container div (slot's parent) with our wrapper
		const slot = root.querySelector("slot");
		const containerDiv = slot?.parentElement as HTMLElement;
		if (containerDiv) {
			containerDiv.replaceWith(wrapper);
		}

		// Get element references
		this.#daysContainer = wrapper.querySelector(".days");
		this.#monthYearEl = wrapper.querySelector(".month-year");
		this.#eventsListEl = wrapper.querySelector(".events-list");
		const prevBtn = wrapper.querySelector(".prev-btn") as HTMLButtonElement;
		const nextBtn = wrapper.querySelector(".next-btn") as HTMLButtonElement;
		const closeBtn = wrapper.querySelector(".close-btn") as HTMLButtonElement;

		// Navigation
		prevBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			this.#currentMonth = new Date(
				this.#currentMonth.getFullYear(),
				this.#currentMonth.getMonth() - 1,
				1
			);
			this.#render();
		});

		nextBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			this.#currentMonth = new Date(
				this.#currentMonth.getFullYear(),
				this.#currentMonth.getMonth() + 1,
				1
			);
			this.#render();
		});

		closeBtn.addEventListener("click", (e) => {
			e.stopPropagation();
			this.dispatchEvent(new CustomEvent("close"));
		});

		// Initial render
		this.#render();

		return root;
	}

	#render() {
		if (!this.#daysContainer || !this.#monthYearEl || !this.#eventsListEl) return;

		const year = this.#currentMonth.getFullYear();
		const month = this.#currentMonth.getMonth();

		// Update month/year display
		const monthNames = [
			"January", "February", "March", "April", "May", "June",
			"July", "August", "September", "October", "November", "December"
		];
		this.#monthYearEl.textContent = `${monthNames[month]} ${year}`;

		// Calculate days
		const firstDay = new Date(year, month, 1);
		const lastDay = new Date(year, month + 1, 0);
		const startPadding = firstDay.getDay();
		const daysInMonth = lastDay.getDate();

		const today = new Date();
		today.setHours(0, 0, 0, 0);

		// Generate day cells
		let html = "";

		// Previous month padding
		const prevMonthLastDay = new Date(year, month, 0).getDate();
		for (let i = startPadding - 1; i >= 0; i--) {
			const day = prevMonthLastDay - i;
			const d = new Date(year, month - 1, day);
			html += `<div class="day other-month" data-date="${d.getFullYear()}-${d.getMonth()}-${d.getDate()}">${day}</div>`;
		}

		// Current month days
		for (let day = 1; day <= daysInMonth; day++) {
			const date = new Date(year, month, day);
			date.setHours(0, 0, 0, 0);

			const classes = ["day"];
			if (date.getTime() === today.getTime()) classes.push("today");
			if (this.#selectedDate && date.getTime() === this.#selectedDate.getTime()) {
				classes.push("selected");
			}
			if (this.#hasEventOnDate(date)) classes.push("has-event");

			html += `<div class="${classes.join(" ")}" data-date="${year}-${month}-${day}">${day}</div>`;
		}

		// Next month padding
		const totalCells = startPadding + daysInMonth;
		const nextPadding = totalCells <= 35 ? 35 - totalCells : 42 - totalCells;
		for (let day = 1; day <= nextPadding; day++) {
			const d = new Date(year, month + 1, day);
			html += `<div class="day other-month" data-date="${d.getFullYear()}-${d.getMonth()}-${d.getDate()}">${day}</div>`;
		}

		this.#daysContainer.innerHTML = html;

		// Add click handlers
		this.#daysContainer.querySelectorAll(".day").forEach((el) => {
			el.addEventListener("click", (e) => {
				e.stopPropagation();
				const dateStr = (el as HTMLElement).dataset.date;
				if (dateStr) {
					const [y, m, d] = dateStr.split("-").map(Number);
					this.selectedDate = new Date(y, m, d);
				}
			});
		});

		// Render events for selected date
		this.#renderEvents();
	}

	#hasEventOnDate(date: Date): boolean {
		return this.#events.some((event) => {
			const eventDate = new Date(event.date);
			eventDate.setHours(0, 0, 0, 0);
			return eventDate.getTime() === date.getTime();
		});
	}

	#renderEvents() {
		if (!this.#eventsListEl) return;

		if (!this.#selectedDate) {
			this.#eventsListEl.style.display = "none";
			return;
		}

		const dayEvents = this.#events.filter((event) => {
			const eventDate = new Date(event.date);
			eventDate.setHours(0, 0, 0, 0);
			const selected = new Date(this.#selectedDate!);
			selected.setHours(0, 0, 0, 0);
			return eventDate.getTime() === selected.getTime();
		});

		if (dayEvents.length === 0) {
			this.#eventsListEl.innerHTML = '<div class="event-item">No events</div>';
		} else {
			this.#eventsListEl.innerHTML = dayEvents
				.map(
					(event) => `
				<div class="event-item">
					<span class="event-dot" style="background: ${event.color || "#8b5cf6"}"></span>
					<span>${this.#escapeHtml(event.title)}</span>
				</div>
			`
				)
				.join("");
		}
		this.#eventsListEl.style.display = "block";
	}

	#escapeHtml(text: string): string {
		const div = document.createElement("div");
		div.textContent = text;
		return div.innerHTML;
	}

	static override fromData(data: Record<string, any>): FolkCalendar {
		const shape = FolkShape.fromData(data) as FolkCalendar;
		if (data.selectedDate) shape.selectedDate = new Date(data.selectedDate);
		if (data.events) shape.events = data.events.map((e: any) => ({ ...e, date: new Date(e.date) }));
		return shape;
	}

	override toJSON() {
		return {
			...super.toJSON(),
			type: "folk-calendar",
			selectedDate: this.selectedDate && !isNaN(this.selectedDate.getTime()) ? this.selectedDate.toISOString() : null,
			events: this.events
				.filter((e) => e.date && !isNaN(e.date.getTime()))
				.map((e) => ({
					...e,
					date: e.date.toISOString(),
				})),
		};
	}

	override applyData(data: Record<string, any>): void {
		super.applyData(data);
		if (data.selectedDate !== undefined) {
			const newDate = data.selectedDate ? new Date(data.selectedDate) : null;
			const curTime = this.selectedDate?.getTime() ?? null;
			const newTime = newDate?.getTime() ?? null;
			if (curTime !== newTime) this.selectedDate = newDate;
		}
		if (data.events !== undefined) {
			this.events = data.events.map((e: any) => ({ ...e, date: new Date(e.date) }));
		}
	}

	/**
	 * Handle system clock heartbeat events.
	 * Refreshes the calendar to update the "today" marker on day boundaries.
	 */
	override onEventReceived(channel: string, payload: unknown): void {
		if (channel === "clock:tick" || channel === "clock:daily") {
			this.#render();
		}
	}
}
