/**
 * Board view operations.
 */
import { PMO_TABLES } from '../schema.js';
import { PMOError, } from '../types.js';
import { slugify } from '../utils.js';
import { getAcceptanceCriteriaSync } from './helpers.js';
const T = PMO_TABLES;
export class ViewStorage {
    ctx;
    constructor(ctx) {
        this.ctx = ctx;
    }
    /**
     * Resolve a project identifier to its actual ID.
     * Tries multiple strategies:
     * 1. Exact ID match
     * 2. Case-insensitive ID match
     * 3. Exact name match
     * 4. Case-insensitive name match
     * 5. Slugified name match
     */
    resolveProjectId(identifier) {
        if (!identifier)
            return null;
        // 1. Exact ID match
        const exactMatch = this.ctx.db.prepare(`
      SELECT id FROM ${T.projects} WHERE id = ?
    `).get(identifier);
        if (exactMatch)
            return exactMatch.id;
        // 2. Case-insensitive ID match
        const caseInsensitiveId = this.ctx.db.prepare(`
      SELECT id FROM ${T.projects} WHERE LOWER(id) = LOWER(?)
    `).get(identifier);
        if (caseInsensitiveId)
            return caseInsensitiveId.id;
        // 3. Exact name match
        const nameMatch = this.ctx.db.prepare(`
      SELECT id FROM ${T.projects} WHERE name = ?
    `).get(identifier);
        if (nameMatch)
            return nameMatch.id;
        // 4. Case-insensitive name match
        const caseInsensitiveName = this.ctx.db.prepare(`
      SELECT id FROM ${T.projects} WHERE LOWER(name) = LOWER(?)
    `).get(identifier);
        if (caseInsensitiveName)
            return caseInsensitiveName.id;
        // 5. Slugified name match
        const allProjects = this.ctx.db.prepare(`
      SELECT id, name FROM ${T.projects}
    `).all();
        const identifierLower = identifier.toLowerCase();
        for (const project of allProjects) {
            const projectSlug = slugify(project.name);
            if (projectSlug === identifierLower || projectSlug === identifier) {
                return project.id;
            }
        }
        return null;
    }
    /**
     * List board views.
     */
    async listBoardViews(filter) {
        let sql = `SELECT * FROM ${T.board_views}`;
        const conditions = [];
        const params = [];
        if (filter?.projectId) {
            conditions.push('project_id = ?');
            params.push(filter.projectId);
        }
        if (filter?.isDefault !== undefined) {
            conditions.push('is_default = ?');
            params.push(filter.isDefault ? 1 : 0);
        }
        if (filter?.search) {
            conditions.push('(name LIKE ? OR description LIKE ?)');
            const searchTerm = `%${filter.search}%`;
            params.push(searchTerm, searchTerm);
        }
        if (conditions.length > 0) {
            sql += ' WHERE ' + conditions.join(' AND ');
        }
        sql += ' ORDER BY is_default DESC, name ASC';
        const rows = this.ctx.db.prepare(sql).all(...params);
        return rows.map((row) => this.rowToBoardView(row));
    }
    /**
     * Get a board view by ID.
     */
    async getBoardView(id) {
        const row = this.ctx.db.prepare(`SELECT * FROM ${T.board_views} WHERE id = ?`).get(id);
        if (!row)
            return null;
        return this.rowToBoardView(row);
    }
    /**
     * Create a new board view.
     */
    async createBoardView(view) {
        if (!view.projectId) {
            throw new PMOError('INVALID', 'Project ID is required');
        }
        if (!view.name) {
            throw new PMOError('INVALID', 'View name is required');
        }
        const id = view.id || slugify(view.name) + '-' + Date.now().toString(36);
        const now = Date.now();
        const filters = JSON.stringify(view.filters || {});
        // If this is set as default, unset other defaults for this project
        if (view.isDefault) {
            this.ctx.db.prepare(`
        UPDATE ${T.board_views} SET is_default = 0 WHERE project_id = ?
      `).run(view.projectId);
        }
        this.ctx.db.prepare(`
      INSERT INTO ${T.board_views} (id, project_id, name, description, is_default, filters, group_by, sort_by, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(id, view.projectId, view.name, view.description || null, view.isDefault ? 1 : 0, filters, view.groupBy || null, view.sortBy || null, now, now);
        return (await this.getBoardView(id));
    }
    /**
     * Update a board view.
     */
    async updateBoardView(id, changes) {
        const existing = await this.getBoardView(id);
        if (!existing) {
            throw new PMOError('NOT_FOUND', `Board view not found: ${id}`);
        }
        const updates = ['updated_at = ?'];
        const params = [Date.now()];
        if (changes.name !== undefined) {
            updates.push('name = ?');
            params.push(changes.name);
        }
        if (changes.description !== undefined) {
            updates.push('description = ?');
            params.push(changes.description || null);
        }
        if (changes.isDefault !== undefined) {
            // If setting as default, unset other defaults for this project
            if (changes.isDefault) {
                this.ctx.db.prepare(`
          UPDATE ${T.board_views} SET is_default = 0 WHERE project_id = ?
        `).run(existing.projectId);
            }
            updates.push('is_default = ?');
            params.push(changes.isDefault ? 1 : 0);
        }
        if (changes.filters !== undefined) {
            updates.push('filters = ?');
            params.push(JSON.stringify(changes.filters));
        }
        if (changes.groupBy !== undefined) {
            updates.push('group_by = ?');
            params.push(changes.groupBy || null);
        }
        if (changes.sortBy !== undefined) {
            updates.push('sort_by = ?');
            params.push(changes.sortBy || null);
        }
        params.push(id);
        this.ctx.db.prepare(`UPDATE ${T.board_views} SET ${updates.join(', ')} WHERE id = ?`).run(...params);
        return (await this.getBoardView(id));
    }
    /**
     * Delete a board view.
     */
    async deleteBoardView(id) {
        const result = this.ctx.db.prepare(`DELETE FROM ${T.board_views} WHERE id = ?`).run(id);
        if (result.changes === 0) {
            throw new PMOError('NOT_FOUND', `Board view not found: ${id}`);
        }
    }
    /**
     * Get the default board view for a project.
     */
    async getDefaultBoardView(projectId) {
        const row = this.ctx.db.prepare(`
      SELECT * FROM ${T.board_views} WHERE project_id = ? AND is_default = 1
    `).get(projectId);
        if (!row)
            return null;
        return this.rowToBoardView(row);
    }
    /**
     * Get board with optional filters applied.
     * @param projectIdOrName - Project ID, name, or slug. Will be resolved to actual ID.
     */
    async getBoardWithView(projectIdOrName, viewId, filters) {
        // Resolve project identifier to actual ID
        const resolvedId = this.resolveProjectId(projectIdOrName);
        if (!resolvedId) {
            throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
        }
        let viewFilters = {};
        let viewSortBy;
        // Load view if specified
        if (viewId) {
            const view = await this.getBoardView(viewId);
            if (view) {
                viewFilters = view.filters;
                viewSortBy = view.sortBy;
            }
        }
        // Override with explicit filters if provided
        const effectiveFilters = { ...viewFilters, ...filters };
        // Get project metadata using resolved ID
        const projectRow = this.ctx.db.prepare(`SELECT * FROM ${T.projects} WHERE id = ?`).get(resolvedId);
        if (!projectRow) {
            throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
        }
        // Get the project's workflow
        const projectMeta = this.ctx.db.prepare(`
      SELECT workflow_id FROM ${T.projects} WHERE id = ?
    `).get(resolvedId);
        const workflowId = projectMeta?.workflow_id || 'default';
        // Get workflow statuses as columns
        const columnRows = this.ctx.db.prepare(`
      SELECT id, workflow_id, name, position
      FROM ${T.workflow_statuses}
      WHERE workflow_id = ?
      ORDER BY position
    `).all(workflowId);
        // Filter columns if columnIds filter is set
        const filteredColumnRows = effectiveFilters.columnIds?.length
            ? columnRows.filter((col) => effectiveFilters.columnIds.includes(col.id))
            : columnRows;
        // Get tickets with filters applied
        const columns = await Promise.all(filteredColumnRows.map(async (col) => {
            const tickets = await this.getTicketsForColumnWithFilters(col.id, resolvedId, effectiveFilters);
            // Apply sorting if specified
            const sortedTickets = viewSortBy ? this.sortTickets(tickets, viewSortBy) : tickets;
            return {
                id: col.id,
                name: col.name,
                position: col.position,
                tickets: sortedTickets,
            };
        }));
        return {
            id: projectRow.id,
            name: projectRow.name,
            columns,
            updatedAt: new Date(projectRow.updated_at),
        };
    }
    /**
     * Get tickets for a column (workflow status) with filters applied.
     */
    async getTicketsForColumnWithFilters(columnId, projectId, filters) {
        // columnId is now a workflow_status id
        let sql = `
      SELECT t.*,
             ws.position as board_position,
             ws.name as column_name,
             ws.name as status_name,
             ws.category as status_category
      FROM ${T.tickets} t
      LEFT JOIN ${T.workflow_statuses} ws ON t.status_id = ws.id
      WHERE t.status_id = ? AND t.project_id = ?
    `;
        const params = [columnId, projectId];
        // Apply filters
        if (filters.assignee !== undefined) {
            if (filters.assignee === 'unassigned') {
                sql += ' AND (t.assignee IS NULL OR t.assignee = "")';
            }
            else {
                sql += ' AND t.assignee = ?';
                params.push(filters.assignee);
            }
        }
        if (filters.owner !== undefined) {
            sql += ' AND t.owner = ?';
            params.push(filters.owner);
        }
        if (filters.priority !== undefined) {
            sql += ' AND UPPER(t.priority) = UPPER(?)';
            params.push(filters.priority);
        }
        if (filters.statusCategory !== undefined) {
            sql += ' AND ws.category = ?';
            params.push(filters.statusCategory);
        }
        if (filters.statusId !== undefined) {
            sql += ' AND t.status_id = ?';
            params.push(filters.statusId);
        }
        if (filters.epicId !== undefined) {
            sql += ' AND t.epic_id = ?';
            params.push(filters.epicId);
        }
        if (filters.search !== undefined) {
            sql += ' AND (t.title LIKE ? OR t.description LIKE ?)';
            const searchTerm = `%${filters.search}%`;
            params.push(searchTerm, searchTerm);
        }
        // Order by priority then created_at
        sql += ` ORDER BY
      CASE t.priority
        WHEN 'P0' THEN 0
        WHEN 'P1' THEN 1
        WHEN 'P2' THEN 2
        WHEN 'P3' THEN 3
        ELSE 4
      END,
      t.created_at ASC`;
        const rows = this.ctx.db.prepare(sql).all(...params);
        return Promise.all(rows.map((row) => this.rowToTicketWithColumn(row)));
    }
    async rowToTicketWithColumn(row) {
        // Get subtasks
        const subtaskRows = this.ctx.db.prepare(`
      SELECT * FROM ${T.subtasks} WHERE ticket_id = ? ORDER BY position
    `).all(row.id);
        const subtasks = subtaskRows.map((s) => ({
            id: s.id,
            title: s.title,
            done: s.done === 1,
        }));
        // Get metadata
        const metadataRows = this.ctx.db.prepare(`
      SELECT key, value FROM ${T.ticket_metadata} WHERE ticket_id = ?
    `).all(row.id);
        const metadata = {};
        for (const m of metadataRows) {
            metadata[m.key] = m.value;
        }
        // Parse labels from JSON
        let labels = [];
        try {
            labels = row.labels ? JSON.parse(row.labels) : [];
        }
        catch {
            labels = [];
        }
        return {
            id: row.id,
            title: row.title,
            description: row.description || undefined,
            priority: row.priority || undefined,
            category: row.category || undefined,
            statusId: row.status_id || '',
            statusName: row.status_name || undefined,
            statusCategory: row.status_category,
            status: row.status,
            owner: row.owner || undefined,
            assignee: row.assignee || undefined,
            branch: row.branch || undefined,
            specId: row.spec_id || undefined,
            epicId: row.epic_id || undefined,
            subtasks,
            labels,
            metadata,
            acceptanceCriteria: getAcceptanceCriteriaSync(this.ctx.db, row.id),
            createdAt: new Date(row.created_at),
            updatedAt: new Date(row.updated_at),
            lastSyncedFromSpec: row.last_synced_from_spec
                ? new Date(row.last_synced_from_spec)
                : undefined,
            lastSyncedFromBoard: row.last_synced_from_board
                ? new Date(row.last_synced_from_board)
                : undefined,
        };
    }
    sortTickets(tickets, sortBy) {
        const sorted = [...tickets];
        switch (sortBy) {
            case 'priority': {
                const priorityOrder = { HIGH: 0, MEDIUM: 1, LOW: 2 };
                sorted.sort((a, b) => {
                    const aOrder = priorityOrder[(a.priority || 'MEDIUM').toUpperCase()] ?? 3;
                    const bOrder = priorityOrder[(b.priority || 'MEDIUM').toUpperCase()] ?? 3;
                    return aOrder - bOrder;
                });
                break;
            }
            case 'created':
                sorted.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
                break;
            case 'updated':
                sorted.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
                break;
            case 'title':
                sorted.sort((a, b) => a.title.localeCompare(b.title));
                break;
            case 'assignee':
                sorted.sort((a, b) => (a.assignee || '').localeCompare(b.assignee || ''));
                break;
        }
        return sorted;
    }
    rowToBoardView(row) {
        return {
            id: row.id,
            projectId: row.project_id,
            name: row.name,
            description: row.description || undefined,
            isDefault: row.is_default === 1,
            filters: row.filters ? JSON.parse(row.filters) : {},
            groupBy: row.group_by,
            sortBy: row.sort_by,
            createdAt: new Date(row.created_at),
            updatedAt: new Date(row.updated_at),
        };
    }
}
