// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
// Planu — Deep iOS project detection (SPEC-013)
import { join, basename } from 'node:path';
import { fileExists } from './detection-utils.js';
import { safeReadFile, safeReaddir, walkForExt } from './mobile-detector-shared.js';
// -- iOS constants -------------------------------------------------------------
const IOS_PRODUCT_TYPES = new Set([
    'com.apple.product-type.application',
    'com.apple.product-type.unit-test.bundle',
    'com.apple.product-type.ui-testing.bundle',
    'com.apple.product-type.framework',
    'com.apple.product-type.app-extension',
    'com.apple.product-type.watchkit-extension',
]);
const IOS_CAP_KEYS = {
    'aps-environment': 'Push Notifications',
    'com.apple.developer.in-app-payments': 'In-App Purchases',
    'com.apple.developer.healthkit': 'HealthKit',
    'com.apple.developer.associated-domains': 'Associated Domains',
    'com.apple.developer.siri': 'Siri',
    'com.apple.developer.maps': 'Maps',
    'com.apple.security.application-groups': 'App Groups',
};
const IOS_FW_SIGNALS = [
    ['import SwiftUI', 'swiftui'],
    ['import UIKit', 'uikit'],
    ['import Combine', 'combine'],
    ['import CoreData', 'coredata'],
    ['import CloudKit', 'cloudkit'],
    ['CKContainer', 'cloudkit'],
];
// -- iOS helpers --------------------------------------------------------------
async function findXcodeDir(projectPath) {
    for (const e of await safeReaddir(projectPath)) {
        if (e.endsWith('.xcworkspace') || e.endsWith('.xcodeproj')) {
            return join(projectPath, e);
        }
    }
    return null;
}
function extractTargets(pbx) {
    const targets = [];
    const re = /\/\*\s*([^*]+?)\s*\*\/\s*=\s*\{[^}]*productType\s*=\s*"([^"]+)"/g;
    let m = re.exec(pbx);
    while (m !== null) {
        const name = m[1]?.trim();
        const ptype = m[2];
        if (name && ptype && IOS_PRODUCT_TYPES.has(ptype)) {
            targets.push(name);
        }
        m = re.exec(pbx);
    }
    return [...new Set(targets)];
}
async function extractSchemes(dir) {
    const entries = await safeReaddir(join(dir, 'xcshareddata', 'xcschemes'));
    return entries.filter((e) => e.endsWith('.xcscheme')).map((e) => basename(e, '.xcscheme'));
}
function extractBuildConfigs(pbx) {
    const configs = new Set(['Debug', 'Release']);
    const re = /\/\*\s*(Debug|Release|[A-Z][a-zA-Z0-9]+)\s*\*\/\s*=\s*\{[^}]*isa\s*=\s*XCBuildConfiguration/g;
    let m = re.exec(pbx);
    while (m !== null) {
        const n = m[1]?.trim();
        if (n) {
            configs.add(n);
        }
        m = re.exec(pbx);
    }
    return [...configs];
}
async function extractCapabilities(projectPath) {
    const caps = new Set();
    async function scanDir(dir) {
        for (const e of await safeReaddir(dir)) {
            if (!e.endsWith('.entitlements')) {
                continue;
            }
            const c = await safeReadFile(join(dir, e));
            if (!c) {
                continue;
            }
            for (const [key, label] of Object.entries(IOS_CAP_KEYS)) {
                if (c.includes(key)) {
                    caps.add(label);
                }
            }
            if (c.includes('NSHealthShareUsageDescription')) {
                caps.add('HealthKit');
            }
        }
    }
    await scanDir(projectPath);
    const top = await safeReaddir(projectPath);
    await Promise.all(top
        .filter((e) => !e.startsWith('.') && !e.includes('.xcodeproj'))
        .map((e) => scanDir(join(projectPath, e))));
    return [...caps];
}
async function detectIOSPackageMgr(projectPath) {
    if (await fileExists(projectPath, 'Package.swift')) {
        return { pm: 'spm', deps: await parseSPMDeps(projectPath) };
    }
    if (await fileExists(projectPath, 'Podfile.lock')) {
        return { pm: 'cocoapods', deps: await parsePodfileLock(projectPath) };
    }
    if (await fileExists(projectPath, 'Cartfile.resolved')) {
        return { pm: 'carthage', deps: {} };
    }
    return { pm: null, deps: {} };
}
async function parsePodfileLock(projectPath) {
    const content = await safeReadFile(join(projectPath, 'Podfile.lock'));
    if (!content) {
        return {};
    }
    const deps = {};
    const re = /^\s+-\s+([A-Za-z0-9_.-]+)\s+\(([\d.]+)\)/gm;
    let m = re.exec(content);
    while (m !== null) {
        const name = m[1];
        const ver = m[2];
        if (name && ver) {
            deps[name] = ver;
        }
        m = re.exec(content);
    }
    return deps;
}
async function parseSPMDeps(projectPath) {
    const content = await safeReadFile(join(projectPath, 'Package.resolved'));
    if (!content) {
        return {};
    }
    try {
        const parsed = JSON.parse(content);
        const deps = {};
        const obj = parsed.object;
        const rawPins = parsed.pins ?? obj?.pins;
        if (!Array.isArray(rawPins)) {
            return deps;
        }
        const pins = rawPins;
        for (const pin of pins) {
            const p = pin;
            const identity = (p.identity ?? p.package);
            const version = p.state?.version;
            if (identity && version) {
                deps[identity] = version;
            }
        }
        return deps;
    }
    catch {
        return {};
    }
}
async function detectIOSFrameworks(projectPath) {
    const found = new Set();
    const swiftFiles = [];
    await walkForExt(projectPath, '.swift', swiftFiles, 20, 0);
    for (const f of swiftFiles) {
        const c = await safeReadFile(f);
        if (c) {
            for (const [sig, fw] of IOS_FW_SIGNALS) {
                if (c.includes(sig)) {
                    found.add(fw);
                }
            }
        }
    }
    for (const e of await safeReaddir(projectPath)) {
        if (e.endsWith('.xcdatamodeld')) {
            found.add('coredata');
        }
    }
    return [...found];
}
// -- Exported detection functions --------------------------------------------
export async function detectDeepIOS(projectPath) {
    const xcodeDir = await findXcodeDir(projectPath);
    let targets = [];
    let schemes = [];
    let buildConfigs = ['Debug', 'Release'];
    let deploymentTarget = null;
    if (xcodeDir) {
        const pbxContent = await safeReadFile(join(xcodeDir, 'project.pbxproj'));
        if (pbxContent) {
            targets = extractTargets(pbxContent);
            buildConfigs = extractBuildConfigs(pbxContent);
            deploymentTarget = /IPHONEOS_DEPLOYMENT_TARGET\s*=\s*([\d.]+)/m.exec(pbxContent)?.[1] ?? null;
        }
        schemes = await extractSchemes(xcodeDir);
    }
    const [capabilities, { pm: packageManager, deps: dependencies }, frameworks, archPattern] = await Promise.all([
        extractCapabilities(projectPath),
        detectIOSPackageMgr(projectPath),
        detectIOSFrameworks(projectPath),
        detectIOSArchPattern(projectPath),
    ]);
    return {
        targets,
        schemes,
        buildConfigs,
        deploymentTarget,
        capabilities,
        packageManager,
        dependencies,
        frameworks,
        archPattern,
    };
}
export async function detectIOSArchPattern(projectPath) {
    const files = [];
    await walkForExt(projectPath, '.swift', files, 20, 0);
    if (files.length === 0) {
        return null;
    }
    let hasTCA = false;
    let interactor = false, presenter = false, router = false, worker = false, hasVM = false;
    for (const f of files) {
        const c = await safeReadFile(f);
        if (!c) {
            continue;
        }
        if (c.includes('import ComposableArchitecture')) {
            hasTCA = true;
        }
        const name = basename(f, '.swift');
        if (name.endsWith('Interactor')) {
            interactor = true;
        }
        if (name.endsWith('Presenter')) {
            presenter = true;
        }
        if (name.endsWith('Router')) {
            router = true;
        }
        if (name.endsWith('Worker')) {
            worker = true;
        }
        if (name.endsWith('ViewModel') || c.includes('ViewModel')) {
            hasVM = true;
        }
    }
    if (hasTCA) {
        return 'tca';
    }
    if (interactor && presenter && router && worker) {
        return 'clean-swift';
    }
    if (interactor && presenter && router) {
        return 'viper';
    }
    if (hasVM) {
        return 'mvvm';
    }
    return null;
}
//# sourceMappingURL=mobile-detector-ios.js.map