"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.search = search;
const types_1 = require("@playdrop/types");
const commandContext_1 = require("../commandContext");
const errors_1 = require("../errors");
const messages_1 = require("../messages");
const output_1 = require("../output");
const refs_1 = require("../refs");
const CREATOR_USERNAME_FILTER_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
function parseSearchKind(raw) {
    if (!raw || raw.trim().length === 0) {
        return 'all';
    }
    const normalized = raw.trim().toLowerCase();
    if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'asset-spec' || normalized === 'all') {
        return normalized;
    }
    return null;
}
function parsePositiveInteger(raw, label, fallback) {
    if (raw === undefined) {
        return fallback;
    }
    const parsed = typeof raw === 'number' ? raw : Number.parseInt(String(raw), 10);
    if (!Number.isInteger(parsed) || parsed < 0) {
        return null;
    }
    if (label === 'limit' && parsed <= 0) {
        return null;
    }
    return parsed;
}
function parseAssetCategory(raw) {
    if (!raw || raw.trim().length === 0) {
        return undefined;
    }
    const normalized = raw.trim().toUpperCase().replace(/[-\s]/g, '_');
    if (normalized === '3D' || normalized === 'MODEL3D') {
        return 'MODEL_3D';
    }
    if (types_1.ASSET_CATEGORY_VALUES.includes(normalized)) {
        return normalized;
    }
    return null;
}
function parseAssetSubcategory(raw) {
    if (!raw || raw.trim().length === 0) {
        return undefined;
    }
    const normalized = (0, types_1.normalizeAssetSubcategory)(raw);
    return normalized.length > 0 ? normalized : null;
}
function parseSearchSort(raw) {
    if (!raw || raw.trim().length === 0) {
        return undefined;
    }
    const normalized = raw.trim().toLowerCase();
    if (types_1.SEARCH_SORT_VALUES.includes(normalized)) {
        return normalized;
    }
    return null;
}
function toSearchItem(result) {
    if (result.kind === 'app') {
        return {
            kind: 'app',
            ref: `${result.app.creatorUsername}/app/${result.app.name}`,
            targetPath: result.targetPath,
            item: result.app,
        };
    }
    if (result.kind === 'asset') {
        return {
            kind: 'asset',
            ref: `${result.asset.creatorUsername}/asset/${result.asset.name}`,
            targetPath: result.targetPath,
            item: result.asset,
        };
    }
    if (result.kind === 'asset-spec') {
        return {
            kind: 'asset-spec',
            ref: `${result.assetSpec.creatorUsername}/asset-spec/${result.assetSpec.name}`,
            targetPath: result.targetPath,
            item: result.assetSpec,
        };
    }
    if (result.kind === 'pack') {
        return {
            kind: 'asset-pack',
            ref: `${result.pack.creatorUsername}/asset-pack/${result.pack.name}`,
            targetPath: result.targetPath,
            item: result.pack,
        };
    }
    return null;
}
function itemSummary(item) {
    if (item.kind === 'app') {
        return item.item.type.toLowerCase();
    }
    if (item.kind === 'asset') {
        const subcategory = item.item.currentVersion?.subcategory;
        return subcategory ? `${item.item.category.toLowerCase()}/${subcategory}` : item.item.category.toLowerCase();
    }
    if (item.kind === 'asset-spec') {
        return item.item.currentVersionRef ? `current ${item.item.currentVersionRef.split('@')[1] ?? 'none'}` : 'no current version';
    }
    return 'asset-pack';
}
function formatCountValue(value) {
    return value.toLocaleString('en-US');
}
function buildCountsSuffix(item) {
    const parts = [];
    if (item.kind === 'app' && typeof item.item.playCount === 'number') {
        if (typeof item.item.viewCount === 'number') {
            parts.push(`view ${formatCountValue(item.item.viewCount)}`);
        }
        parts.push(`launch ${formatCountValue(item.item.playCount)}`);
    }
    if (item.kind === 'asset') {
        if (typeof item.item.viewCount === 'number') {
            parts.push(`view ${formatCountValue(item.item.viewCount)}`);
        }
    }
    if (item.kind === 'asset-pack' && typeof item.item.downloadCount === 'number') {
        parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
    }
    if (item.kind === 'asset-spec') {
        parts.push(`assets ${formatCountValue(item.item.publicAssetCount)}`);
        parts.push(`apps ${formatCountValue(item.item.supportingAppCount)}`);
        if (item.item.documentationAvailable) {
            parts.push('docs yes');
        }
        return ` | ${parts.join(' | ')}`;
    }
    if (typeof item.item.likeCount === 'number') {
        parts.push(`like ${formatCountValue(item.item.likeCount)}`);
    }
    if (typeof item.item.remixCount === 'number') {
        parts.push(`remix ${formatCountValue(item.item.remixCount)}`);
    }
    if (typeof item.item.commentCount === 'number') {
        parts.push(`comment ${formatCountValue(item.item.commentCount)}`);
    }
    return parts.length > 0 ? ` | ${parts.join(' | ')}` : '';
}
function normalizeOptionalString(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const normalized = value.trim();
    return normalized.length > 0 ? normalized : undefined;
}
function normalizeTagFilters(rawTags) {
    if (!Array.isArray(rawTags) || rawTags.length === 0) {
        return [];
    }
    const normalizedRefs = [];
    const seenRefs = new Set();
    for (const rawTag of rawTags) {
        if (typeof rawTag !== 'string' || rawTag.trim().length === 0) {
            return null;
        }
        let normalizedRef = '';
        try {
            normalizedRef = (0, types_1.normalizeTagRef)(rawTag);
        }
        catch {
            return null;
        }
        if (seenRefs.has(normalizedRef)) {
            return null;
        }
        seenRefs.add(normalizedRef);
        normalizedRefs.push(normalizedRef);
    }
    if (normalizedRefs.length > 5) {
        return null;
    }
    return normalizedRefs;
}
function buildTagSuffix(tags) {
    const normalizedTags = Array.isArray(tags) ? tags : [];
    if (normalizedTags.length === 0) {
        return '';
    }
    const label = normalizedTags
        .slice(0, 3)
        .map((tag) => tag.ref)
        .join(', ');
    return ` | tags ${label}`;
}
function buildSearchRequest(input) {
    return {
        q: input.trimmedQuery,
        mode: 'flat',
        kind: (0, refs_1.contentKindToSearchKind)(input.kind),
        page: 1,
        pageSize: input.limit + input.offset,
        ...(input.sort ? { sort: input.sort } : {}),
        ...(input.creatorUsername ? { creatorUsername: input.creatorUsername } : {}),
        appType: input.appType,
        auth: input.auth,
        controller: input.controller,
        surface: input.surface,
        assetCategory: input.assetCategory,
        assetSubcategory: input.assetSubcategory,
        assetSpec: input.assetSpec,
        assetSpecOwner: input.assetSpecOwner,
        assetSpecName: input.assetSpecName,
        packContainsCategory: input.packContainsCategory,
        packContainsSubcategory: input.packContainsSubcategory,
        tags: input.tags,
    };
}
async function search(query, options = {}) {
    const trimmedQuery = typeof query === 'string' ? query.trim() : '';
    const creatorUsername = normalizeOptionalString(options.creator)?.toLowerCase();
    if (creatorUsername && !CREATOR_USERNAME_FILTER_REGEX.test(creatorUsername)) {
        (0, messages_1.printErrorWithHelp)('The --creator value is invalid.', ['Use a creator username like playdrop or olivier-dev.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const hasScopeFilters = Boolean(creatorUsername)
        || Boolean(options.appType)
        || Boolean(options.auth)
        || Boolean(options.controller)
        || Boolean(options.surface)
        || Boolean(options.assetCategory)
        || Boolean(options.assetSubcategory)
        || Boolean(options.assetSpec)
        || Boolean(options.assetSpecOwner)
        || Boolean(options.assetSpecName)
        || Boolean(options.packContainsCategory)
        || Boolean(options.packContainsSubcategory)
        || Array.isArray(options.tag) && options.tag.length > 0;
    if (!trimmedQuery && !hasScopeFilters) {
        (0, messages_1.printErrorWithHelp)('A search query or filter is required.', ['Example: playdrop search "city builder"', 'Example: playdrop search --creator playdrop --kind app'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const kind = parseSearchKind(options.kind);
    if (!kind) {
        (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, asset-spec, all.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const limit = parsePositiveInteger(options.limit, 'limit', 10);
    if (limit === null) {
        (0, messages_1.printErrorWithHelp)('The --limit value must be a positive integer.', ['Example: --limit 10'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const offset = parsePositiveInteger(options.offset, 'offset', 0);
    if (offset === null) {
        (0, messages_1.printErrorWithHelp)('The --offset value must be zero or a positive integer.', ['Example: --offset 20'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const sort = parseSearchSort(options.sort);
    if (sort === null) {
        (0, messages_1.printErrorWithHelp)(`Sort "${options.sort ?? ''}" is not supported.`, ['Use one of: relevance, views, likes, used, newest, updated, downloads, remixes, comments, assets, apps, alpha.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const appType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
    if (options.appType !== undefined && !appType) {
        (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const auth = options.auth === undefined ? undefined : (0, types_1.parseAppAuthFilter)(options.auth);
    if (options.auth !== undefined && !auth) {
        (0, messages_1.printErrorWithHelp)(`Auth filter "${options.auth}" is not supported.`, ['Use one of: required, optional, none.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const controller = options.controller === undefined ? undefined : (0, types_1.parseAppControllerFilter)(options.controller);
    if (options.controller !== undefined && !controller) {
        (0, messages_1.printErrorWithHelp)(`Controller filter "${options.controller}" is not supported.`, ['Use one of: required, optional, none.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const surface = options.surface === undefined ? undefined : (0, types_1.parseAppSurfaceFilter)(options.surface);
    if (options.surface !== undefined && !surface) {
        (0, messages_1.printErrorWithHelp)(`Surface filter "${options.surface}" is not supported.`, ['Use one of: desktop, mobile-portrait, mobile-landscape.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const hasCapabilityFilters = Boolean(auth || controller || surface);
    if (hasCapabilityFilters && kind !== 'all' && kind !== 'app') {
        (0, messages_1.printErrorWithHelp)('Capability filters only support app search.', ['Use --kind app, or omit --kind to search across app results only.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const assetCategory = parseAssetCategory(options.assetCategory);
    if (assetCategory === null) {
        (0, messages_1.printErrorWithHelp)(`Asset category "${options.assetCategory}" is not supported.`, ['Use a canonical asset category like IMAGE, VIDEO, AUDIO, SPRITESHEET, or MODEL_3D.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const assetSubcategory = parseAssetSubcategory(options.assetSubcategory);
    if (assetSubcategory === null) {
        (0, messages_1.printErrorWithHelp)(`Asset subcategory "${options.assetSubcategory}" is invalid.`, ['Use lowercase slug values like generic, avatar, music, or sfx.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const packContainsCategory = parseAssetCategory(options.packContainsCategory);
    if (packContainsCategory === null) {
        (0, messages_1.printErrorWithHelp)(`Pack category "${options.packContainsCategory}" is not supported.`, ['Use a canonical asset category like IMAGE, VIDEO, AUDIO, SPRITESHEET, or MODEL_3D.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const packContainsSubcategory = parseAssetSubcategory(options.packContainsSubcategory);
    if (packContainsSubcategory === null) {
        (0, messages_1.printErrorWithHelp)(`Pack subcategory "${options.packContainsSubcategory}" is invalid.`, ['Use lowercase slug values like generic, avatar, music, or sfx.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    if (hasCapabilityFilters && (assetCategory
        || assetSubcategory
        || normalizeOptionalString(options.assetSpec)
        || normalizeOptionalString(options.assetSpecOwner)
        || normalizeOptionalString(options.assetSpecName)
        || packContainsCategory
        || packContainsSubcategory)) {
        (0, messages_1.printErrorWithHelp)('Capability filters cannot be combined with asset or pack search filters.', ['Use app search filters only when passing --auth, --surface, or --controller.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    const tags = normalizeTagFilters(options.tag);
    if (tags === null) {
        (0, messages_1.printErrorWithHelp)('The --tag value is invalid.', ['Use canonical refs like theme/pirate or visual-style/pixel-art.', 'Pass at most 5 unique tag refs.'], { command: 'search' });
        process.exitCode = 1;
        return;
    }
    await (0, commandContext_1.withPublicEnvironment)('search', async ({ client }) => {
        try {
            const response = await client.search(buildSearchRequest({
                trimmedQuery,
                kind,
                ...(creatorUsername ? { creatorUsername } : {}),
                limit,
                offset,
                sort: sort ?? undefined,
                appType: appType,
                auth: auth ?? undefined,
                controller: controller ?? undefined,
                surface: surface ?? undefined,
                assetCategory: assetCategory ?? undefined,
                assetSubcategory: assetSubcategory ?? undefined,
                assetSpec: normalizeOptionalString(options.assetSpec),
                assetSpecOwner: normalizeOptionalString(options.assetSpecOwner),
                assetSpecName: normalizeOptionalString(options.assetSpecName),
                packContainsCategory: packContainsCategory ?? undefined,
                packContainsSubcategory: packContainsSubcategory ?? undefined,
                tags,
            }));
            const normalizedItems = (response.results ?? [])
                .map((result) => toSearchItem(result))
                .filter((item) => Boolean(item));
            const items = normalizedItems.slice(offset, offset + limit);
            const pagination = {
                limit,
                offset,
                count: items.length,
                hasMore: normalizedItems.length > offset + limit || Boolean(response.pagination?.hasMore),
            };
            if (options.json) {
                (0, output_1.printJson)({ items, pagination });
                return;
            }
            if (items.length === 0) {
                console.log(trimmedQuery ? `No results found for "${trimmedQuery}".` : 'No results found.');
                console.log('Next: adjust your filters or run "playdrop browse" to explore available content.');
                return;
            }
            console.log(trimmedQuery ? `Search results for "${trimmedQuery}":\n` : 'Search results:\n');
            for (const [index, item] of items.entries()) {
                const displayName = item.item.displayName || item.item.name;
                console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}${buildTagSuffix(item.item.tags)}`);
            }
            console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one result.');
        }
        catch (error) {
            const handled = (0, errors_1.handleCommandFailure)(error, 'search', 'Search', {
                apiMessage: (apiError) => {
                    if (apiError.code === 'invalid_tag_ref' || apiError.code === 'duplicate_tag_ref' || apiError.code === 'tag_filter_limit_exceeded') {
                        return {
                            problem: 'One or more tag filters are invalid.',
                            suggestions: ['Use canonical refs like theme/pirate.', 'Pass at most 5 unique --tag values.'],
                        };
                    }
                    return {
                        problem: `Search failed with status ${apiError.status}.`,
                        suggestions: ['Retry in a moment.', 'Use "playdrop help search" for usage details.'],
                    };
                },
            });
            if (!handled) {
                throw error;
            }
        }
    });
}
