/*
 * Copyright (c) 2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as childProcess from 'child_process';
import * as vscode from 'vscode';
import { getCjmpProjectWorkspaceFolder } from './cjmpProjectContext';
import { resolveKeelsCommand } from './cjmpSdk';
import { getCjmpOutputChannel } from './cjmpOutputChannel';
import { getProjectType } from './utils';

export type CleanPlatform = 'all' | 'android' | 'hos' | 'ios';
type SpecificCleanPlatform = Exclude<CleanPlatform, 'all'>;

function isSpecificCleanPlatform(value: CleanPlatform): value is SpecificCleanPlatform {
  return value !== 'all';
}

type PlatformSelection =
  | { kind: 'all' }
  | { kind: 'specific'; values: readonly SpecificCleanPlatform[] };
export type CleanMode = 'normal' | 'deep' | 'test-results' | 'deep-test-results';

interface CleanPlatformOption extends vscode.QuickPickItem {
  value: CleanPlatform;
}

interface SpecificCleanPlatformOption extends vscode.QuickPickItem {
  value: SpecificCleanPlatform;
}

interface CleanModeOption extends vscode.QuickPickItem {
  value: CleanMode;
}

interface CleanCommandContext {
  executablePath: string;
  environment: NodeJS.ProcessEnv;
}

export interface CleanPrompt {
  pickPlatforms(
    items: readonly CleanPlatformOption[],
    options: vscode.QuickPickOptions & { canPickMany: true },
  ): Promise<readonly CleanPlatformOption[] | undefined>;
  pickMode(
    items: readonly CleanModeOption[],
    options: vscode.QuickPickOptions,
  ): Promise<CleanModeOption | undefined>;
  confirm(message: string): Promise<boolean>;
}

export interface CleanProcessRunner {
  run(
    executablePath: string,
    args: readonly string[],
    options: childProcess.SpawnOptionsWithoutStdio,
    onOutput: (output: string) => void,
  ): Promise<number | null>;
}

export interface CleanDependencies {
  prompt: CleanPrompt;
  processRunner: CleanProcessRunner;
  resolveCommand: () => CleanCommandContext | undefined;
  createOutputChannel: () => vscode.OutputChannel;
}

const SUPPORTED_CLEAN_PLATFORMS: readonly SpecificCleanPlatform[] = ['android', 'hos', 'ios'];

const PLATFORM_OPTIONS: readonly CleanPlatformOption[] = [
  { label: 'All Platforms', value: 'all' },
  { label: 'Android', value: 'android' },
  { label: 'HarmonyOS', value: 'hos' },
  { label: 'iOS', value: 'ios' },
];

const MODE_OPTIONS: readonly CleanModeOption[] = [
  { label: 'Normal Clean', value: 'normal' },
  { label: 'Deep Clean', value: 'deep' },
  { label: 'Clean Including Test Results', value: 'test-results' },
  { label: 'Deep Clean Including Test Results', value: 'deep-test-results' },
];

function normalizePlatformSelection(
  selection: readonly CleanPlatformOption[],
): PlatformSelection {
  const specificPlatforms = [...new Set(
    selection
      .map((item) => item.value)
      .filter(isSpecificCleanPlatform),
  )];

  return specificPlatforms.length > 0
    ? { kind: 'specific', values: specificPlatforms }
    : { kind: 'all' };
}

function getSelectedPlatformOptions(
  selection: PlatformSelection,
  allOption: CleanPlatformOption,
  specificOptions: readonly SpecificCleanPlatformOption[],
): readonly CleanPlatformOption[] {
  if (selection.kind === 'all') {
    return [allOption];
  }
  return specificOptions.filter((item) => selection.values.includes(item.value));
}

function hasSamePlatformOptions(
  current: readonly CleanPlatformOption[],
  expected: readonly CleanPlatformOption[],
): boolean {
  return current.length === expected.length
    && current.every((item) => expected.some((expectedItem) => expectedItem.value === item.value));
}

function showPlatformQuickPick(
  items: readonly CleanPlatformOption[],
  options: vscode.QuickPickOptions & { canPickMany: true },
): Promise<readonly CleanPlatformOption[] | undefined> {
  return new Promise((resolve) => {
    const quickPick = vscode.window.createQuickPick<CleanPlatformOption>();
    const allOption = items.find((item) => item.value === 'all');
    const specificOptions: readonly SpecificCleanPlatformOption[] = items.filter(
      (item): item is SpecificCleanPlatformOption => item.value !== 'all',
    );
    if (!allOption) {
      quickPick.dispose();
      resolve(undefined);
      return;
    }

    let platformSelection: PlatformSelection = { kind: 'all' };
    let accepted = false;
    let hidden = false;

    const getExpectedSelection = () => getSelectedPlatformOptions(
      platformSelection,
      allOption,
      specificOptions,
    );

    quickPick.placeholder = options.placeHolder;
    quickPick.canSelectMany = true;
    quickPick.items = [allOption, ...specificOptions];
    quickPick.selectedItems = getExpectedSelection();

    quickPick.onDidChangeSelection((selection) => {
      if (hidden) {
        return;
      }

      platformSelection = normalizePlatformSelection(selection);
      const expectedSelection = getExpectedSelection();
      if (!hasSamePlatformOptions(selection, expectedSelection)) {
        // Keep the item list stable. Updating selectedItems alone avoids the
        // Windows QuickPick flicker caused by replacing items in this event.
        quickPick.selectedItems = expectedSelection;
      }
    });

    quickPick.onDidAccept(() => {
      const result = getExpectedSelection();
      accepted = true;
      resolve(result);
      quickPick.hide();
    });
    quickPick.onDidHide(() => {
      hidden = true;
      if (!accepted) {
        resolve(undefined);
      }
      quickPick.dispose();
    });
    quickPick.show();
  });
}

const defaultCleanDependencies: CleanDependencies = {
  prompt: {
    pickPlatforms: showPlatformQuickPick,
    pickMode: async (items, options) => vscode.window.showQuickPick(items, options),
    confirm: async (message) => {
      const selection = await vscode.window.showWarningMessage(
        message,
        { modal: true },
        'Clean',
      );
      return selection === 'Clean';
    },
  },
  processRunner: {
    run: (executablePath, args, options, onOutput) => new Promise((resolve, reject) => {
      const spawnExecutable = process.platform === 'win32'
        ? `"${executablePath}"`
        : executablePath;
      const cleanProcess = childProcess.spawn(spawnExecutable, [...args], {
        ...options,
        shell: process.platform === 'win32',
      });

      cleanProcess.stdout?.on('data', (data) => onOutput(data.toString()));
      cleanProcess.stderr?.on('data', (data) => onOutput(data.toString()));
      cleanProcess.on('error', reject);
      cleanProcess.on('close', resolve);
    }),
  },
  resolveCommand: () => resolveKeelsCommand(),
  createOutputChannel: () => getCjmpOutputChannel(),
};

export function getCleanCommandArgs(
  platforms: readonly CleanPlatform[],
  mode: CleanMode,
): string[] {
  const args = ['clean'];
  const specificPlatforms = [...new Set(
    platforms
      .filter(isSpecificCleanPlatform)
      .filter((platform) => SUPPORTED_CLEAN_PLATFORMS.includes(platform)),
  )];
  const cleansAllPlatforms = platforms.includes('all')
    || SUPPORTED_CLEAN_PLATFORMS.every((platform) => specificPlatforms.includes(platform));

  if (!cleansAllPlatforms) {
    args.push('--platform', specificPlatforms.join(','));
  }
  if (mode === 'deep' || mode === 'deep-test-results') {
    args.push('--deep');
  }
  if (mode === 'test-results' || mode === 'deep-test-results') {
    args.push('--test-results');
  }

  args.push('-v');
  return args;
}

export async function cleanCjmpProject(
  dependencies: CleanDependencies = defaultCleanDependencies,
): Promise<void> {
  const workspaceFolder = getCjmpProjectWorkspaceFolder(vscode.workspace.workspaceFolders);
  if (!workspaceFolder) {
    vscode.window.showErrorMessage('The opened folder is not a valid CJMP project.');
    return;
  }
  if (getProjectType() === 'logic-module') {
    vscode.window.showErrorMessage('CJMP clean does not support logic-module projects.');
    return;
  }
  const workspaceRoot = workspaceFolder.uri.fsPath;

  const platforms = await dependencies.prompt.pickPlatforms(PLATFORM_OPTIONS, {
    placeHolder: 'Select platforms to clean',
    canPickMany: true,
  });
  if (!platforms || platforms.length === 0) {
    return;
  }

  const mode = await dependencies.prompt.pickMode(MODE_OPTIONS, {
    placeHolder: 'Select the clean mode',
  });
  if (!mode) {
    return;
  }

  const platformLabels = platforms.map((platform) => platform.label).join(', ');
  const confirmed = await dependencies.prompt.confirm(
    `Clean generated files for ${platformLabels} using ${mode.label}?`,
  );
  if (!confirmed) {
    return;
  }

  const keelsCommand = dependencies.resolveCommand();
  if (!keelsCommand) {
    return;
  }

  const args = getCleanCommandArgs(
    platforms.map((platform) => platform.value),
    mode.value,
  );
  const outputChannel = dependencies.createOutputChannel();
  outputChannel.show(true);
  outputChannel.appendLine('');
  outputChannel.appendLine('=== CJMP Clean ===');
  outputChannel.appendLine(`> ${keelsCommand.executablePath} ${args.join(' ')}`);

  try {
    const exitCode = await dependencies.processRunner.run(
      keelsCommand.executablePath,
      args,
      {
        cwd: workspaceRoot,
        env: keelsCommand.environment,
      },
      (output) => outputChannel.append(output),
    );

    if (exitCode === 0) {
      vscode.window.showInformationMessage('CJMP clean completed successfully.');
    } else {
      vscode.window.showErrorMessage(
        `CJMP clean failed. Exit code: ${exitCode ?? 'unknown'}. See the CJMP Clean output for details.`,
      );
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    outputChannel.appendLine(`Error: ${message}`);
    vscode.window.showErrorMessage(`Failed to execute CJMP clean: ${message}`);
  }
}