/**
 * `openspec context` (slice 4.1): the working set a root's declarations
 * describe, as an agent brief (JSON), a human listing, or an editor
 * view (`--code-workspace`). Assembly is presentation over the Phase 3
 * relationship data; doctor is the health surface. The only write this
 * command can perform is the explicitly requested workspace file.
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { Command, Option } from 'commander';

import {
  resolveRootForCommand,
  type ResolvedOpenSpecRoot,
} from '../core/root-selection.js';
import { inspectRelationships } from '../core/relationship-health.js';
import {
  assembleWorkingSet,
  buildCodeWorkspaceJson,
  isAvailableMember,
  type WorkingSet,
  type WorkingSetMember,
} from '../core/working-set.js';
import { StoreError } from '../core/store/errors.js';
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
import { COMMON_FLAGS } from '../core/completions/shared-flags.js';
import { emitFailure, printJson } from './shared-output.js';
import { gatherRelationshipData } from './shared-gather.js';

const FAILURE_PAYLOAD = { root: null, members: [] };

async function gatherWorkingSet(
  root: ResolvedOpenSpecRoot
): Promise<{ workingSet: WorkingSet; declaredReferenceCount: number }> {
  const data = await gatherRelationshipData(root);

  // Reuse the 3.6 composition for member classification; the
  // doctor-only wrong-turn detections and store facts are deliberately
  // absent — doctor is the health surface.
  const health = inspectRelationships({
    root,
    rootHealthy: data.rootInspection.healthy,
    rootStatus: data.rootInspection.diagnostics,
    referenceEntries: data.referenceEntries,
    registryUnreadable: data.registrySnapshot.unreadable,
  });

  return {
    workingSet: assembleWorkingSet({
      root,
      referenceEntries: data.referenceEntries,
      topLevelStatus: health.status,
    }),
    declaredReferenceCount: data.projectConfig?.references?.length ?? 0,
  };
}

function memberLine(member: WorkingSetMember): string {
  return `  ${member.id}  ${member.path}`;
}

function printHumanWorkingSet(workingSet: WorkingSet, declaredReferenceCount: number): void {
  const rootLabel = workingSet.root.store_id ?? path.basename(workingSet.root.path);
  console.log(`Working context for ${rootLabel} (${workingSet.root.path})`);
  console.log('');
  console.log('OpenSpec root');
  console.log(`  ${rootLabel}  ${workingSet.root.path}`);

  const availableStores = workingSet.members.filter(
    (member) => member.role === 'referenced_store' && isAvailableMember(member)
  );
  const unavailable = workingSet.members.filter((member) => !isAvailableMember(member));

  if (availableStores.length > 0) {
    console.log('');
    console.log('Referenced stores');
    for (const member of availableStores) {
      console.log(memberLine(member));
      if (member.fetch) {
        console.log(`    Fetch: ${member.fetch}`);
      }
    }
  }

  if (workingSet.members.length === 0) {
    console.log('');
    // Self-references are silently omitted from the index; an
    // emptied-by-omission set must not claim nothing was declared.
    console.log(
      declaredReferenceCount > 0
        ? 'Declared references all resolve to this root; the working set is this root alone.'
        : 'No references declared; the working set is this root alone.'
    );
  }

  if (unavailable.length > 0 || workingSet.status.length > 0) {
    console.log('');
    console.log('Not available on this machine');
    for (const member of unavailable) {
      if (member.status.length === 0) {
        console.log(`  - ${member.id}`);
        continue;
      }
      for (const diagnostic of member.status) {
        console.log(`  - ${member.id}: ${diagnostic.message}`);
        if (diagnostic.fix) {
          console.log(`    Fix: ${diagnostic.fix}`);
        }
      }
    }
    for (const diagnostic of workingSet.status) {
      console.log(`  Note: ${diagnostic.message}`);
      if (diagnostic.fix) {
        console.log(`  Fix: ${diagnostic.fix}`);
      }
    }
  }
}

function writeCodeWorkspace(
  workingSet: WorkingSet,
  outputPath: string,
  force: boolean
): void {
  const resolved = path.resolve(outputPath);
  if (fs.existsSync(resolved) && !force) {
    throw new StoreError(
      `Refusing to overwrite ${resolved}.`,
      'context_file_exists',
      {
        target: 'context.output',
        fix: `Pass --force to overwrite, or choose a different path.`,
      }
    );
  }
  const parent = path.dirname(resolved);
  if (!fs.existsSync(parent)) {
    throw new StoreError(
      `Output directory does not exist: ${parent}.`,
      'context_output_dir_missing',
      { target: 'context.output', fix: 'Create the directory first, or choose another path.' }
    );
  }

  const rootName = workingSet.root.store_id ?? path.basename(workingSet.root.path);
  fs.writeFileSync(resolved, buildCodeWorkspaceJson(workingSet, rootName));

  const available = workingSet.members.filter(isAvailableMember).length;
  const skipped = workingSet.members
    .filter((member) => !isAvailableMember(member))
    .map((member) => member.id);
  const summary =
    skipped.length > 0
      ? `Wrote ${resolved} (${available + 1} folders; not available: ${skipped.join(', ')})`
      : `Wrote ${resolved} (${available + 1} folders)`;
  // stderr keeps JSON stdout pure; for humans it reads inline.
  console.error(summary);
}

export function registerContextCommand(program: Command): void {
  const description =
    COMMAND_REGISTRY.find((entry) => entry.name === 'context')?.description ??
    'Print the working context for the resolved OpenSpec root';

  program
    .command('context')
    .description(description)
    .option('--store <id>', COMMON_FLAGS.store.description)
    .addOption(
      new Option('--store-path <path>', 'Removed; register the store and use --store').hideHelp()
    )
    .option('--json', 'Output the agent brief as JSON')
    .option('--code-workspace <path>', 'Also write a VS Code workspace file for the set')
    .option('--force', 'Overwrite an existing --code-workspace file')
    .action(
      async (options: {
        store?: string;
        storePath?: string;
        json?: boolean;
        codeWorkspace?: string;
        force?: boolean;
      }) => {
        try {
          const root = await resolveRootForCommand(
            { store: options.store, storePath: options.storePath },
            { json: options.json, failurePayload: FAILURE_PAYLOAD, allowImplicitRoot: false }
          );
          if (!root) {
            return;
          }

          const { workingSet, declaredReferenceCount } = await gatherWorkingSet(root);

          if (options.json) {
            // The write runs FIRST: a write failure must leave stdout
            // holding exactly one JSON document (the failure payload).
            if (options.codeWorkspace) {
              writeCodeWorkspace(workingSet, options.codeWorkspace, options.force === true);
            }
            printJson(workingSet);
          } else {
            printHumanWorkingSet(workingSet, declaredReferenceCount);
            if (options.codeWorkspace) {
              writeCodeWorkspace(workingSet, options.codeWorkspace, options.force === true);
            }
          }
        } catch (error) {
          emitFailure(options.json, FAILURE_PAYLOAD, error, 'context_failed');
        }
      }
    );
}


/*
 * Copyright 2024 Netflix, Inc.
 *
 * 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.
 */
package com.netflix.sel.type;

import static org.junit.Assert.*;

import com.netflix.sel.visitor.SelOp;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;

public class SelJodaDateTimeZoneTest {

  private SelJodaDateTimeZone one;
  private SelJodaDateTimeZone another;

  @Before
  public void setUp() throws Exception {
    one = SelJodaDateTimeZone.of(DateTimeZone.UTC);
    another = SelJodaDateTimeZone.of(DateTimeZone.forID("America/Los_Angeles"));
  }

  @Test
  public void testAssignOps() {
    one.assignOps(SelOp.ASSIGN, another);
    assertEquals("DATETIME_ZONE: America/Los_Angeles", one.type() + ": " + one);
  }

  @Test(expected = IllegalArgumentException.class)
  public void testInvalidAssignType() {
    one.assignOps(SelOp.ASSIGN, SelString.of("foo"));
  }

  @Test(expected = UnsupportedOperationException.class)
  public void testInvalidAssignOps() {
    one.assignOps(SelOp.ADD_ASSIGN, SelString.of("foo"));
  }

  @Test
  public void testCalls() {
    SelType res = one.call("forID", new SelType[] {SelString.of("UTC")});
    assertEquals("DATETIME_ZONE: UTC", res.type() + ": " + res);
    res =
        one.call(
            "getOffset",
            new SelType[] {
              SelJodaDateTime.of(new DateTime(0, DateTimeZone.forID("America/Los_Angeles")))
            });
    assertEquals("LONG: 0", res.type() + ": " + res);
    res =
        another.call(
            "getOffset",
            new SelType[] {
              SelJodaDateTime.of(new DateTime(0, DateTimeZone.forID("America/Los_Angeles")))
            });
    assertEquals("LONG: -28800000", res.type() + ": " + res);
  }

  @Test(expected = UnsupportedOperationException.class)
  public void testInvalidCall() {
    one.call("invalid", new SelType[] {SelLong.of(123)});
  }

  @Test
  public void testField() {
    SelType res = one.field(SelString.of("UTC"));
    assertEquals("DATETIME_ZONE: UTC", res.type() + ": " + res);
  }

  @Test(expected = UnsupportedOperationException.class)
  public void testInvalidField() {
    one.field(SelString.of("ABC"));
  }
}


import { JSDOM } from 'jsdom';
import { describe, it, expect, vi } from 'vitest';
import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';

vi.mock('node:fs', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        statSync: vi.fn((input) => {
            const value = String(input);
            if (value.includes('missing')) return undefined;
            return { isFile: () => !value.includes('directory') };
        }),
    };
});

vi.mock('node:path', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        resolve: vi.fn((input) => `/abs/${input}`),
        extname: vi.fn((input) => {
            const match = String(input).match(/\.[^.]+$/);
            return match ? match[0] : '';
        }),
    };
});

import { __test__, publishCommand } from './publish.js';

function makePage({ evaluateResults = [], overrides = {} } = {}) {
    const evaluate = vi.fn();
    for (const result of evaluateResults) {
        evaluate.mockResolvedValueOnce(result);
    }
    evaluate.mockResolvedValue({ ok: false, reason: 'unknown-state' });

    return {
        goto: vi.fn().mockResolvedValue(undefined),
        wait: vi.fn().mockResolvedValue(undefined),
        evaluate,
        setFileInput: vi.fn().mockResolvedValue(undefined),
        getCurrentUrl: vi.fn().mockResolvedValue('https://www.goofish.com/publish'),
        ...overrides,
    };
}

async function runBrowserScript(html, script, { url = 'https://www.goofish.com/publish' } = {}) {
    const dom = new JSDOM(html, { url, runScripts: 'outside-only' });
    return dom.window.eval(script);
}

const validArgs = {
    title: 'MacBook Pro',
    description: '成色很好，功能正常',
    price: '5999.99',
    condition: '轻微使用',
    category: '笔记本',
};

describe('xianyu/publish', () => {
    it('builds the goofish publish URL', () => {
        expect(__test__.buildPublishUrl()).toBe('https://www.goofish.com/publish');
    });

    it('validates publish arguments before navigation', async () => {
        const page = makePage();

        await expect(publishCommand.func(page, { ...validArgs, title: '   ' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, price: '0' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, price: '12.345' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, condition: '八成新' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, images: 'a.bmp' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, images: 'missing.png' })).rejects.toBeInstanceOf(ArgumentError);
        await expect(publishCommand.func(page, { ...validArgs, images: '1.png,2.png,3.png,4.png,5.png,6.png,7.png,8.png,9.png,10.png' })).rejects.toBeInstanceOf(ArgumentError);
        expect(page.goto).not.toHaveBeenCalled();
    });

    it('publishes when every UI step has positive proof', async () => {
        const page = makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: true, filled: ['title', 'description', 'price', 'condition'], missing: [] },
                { ok: true },
                { status: 'published', item_id: '123456789012', url: 'https://www.goofish.com/item?id=123456789012' },
            ],
        });

        const rows = await publishCommand.func(page, validArgs);

        expect(rows).toEqual([{
            status: 'published',
            item_id: '123456789012',
            title: 'MacBook Pro',
            price: '¥5999.99',
            condition: '轻微使用',
            url: 'https://www.goofish.com/item?id=123456789012',
            message: '发布成功',
        }]);
    });

    it('uses IPage getCurrentUrl instead of a non-existent page.url method', async () => {
        const page = makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: true, filled: ['title', 'description', 'price', 'condition'], missing: [] },
                { ok: true },
                { status: 'published', item_id: '123456789012' },
            ],
            overrides: {
                getCurrentUrl: vi.fn().mockResolvedValue('https://www.goofish.com/item?id=123456789012'),
            },
        });

        expect(page.url).toBeUndefined();

        const rows = await publishCommand.func(page, validArgs);

        expect(page.getCurrentUrl).toHaveBeenCalled();
        expect(rows[0].url).toBe('https://www.goofish.com/item?id=123456789012');
    });

    it('maps login walls to AuthRequiredError', async () => {
        const page = makePage({
            evaluateResults: [
                { requiresAuth: true },
            ],
        });

        await expect(publishCommand.func(page, validArgs)).rejects.toBeInstanceOf(AuthRequiredError);
    });

    it('fails fast when category selection or form filling is not proven', async () => {
        await expect(publishCommand.func(makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: false, reason: 'category-not-found' },
            ],
        }), validArgs)).rejects.toBeInstanceOf(CommandExecutionError);

        await expect(publishCommand.func(makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: false, missing: ['price'] },
            ],
        }), validArgs)).rejects.toBeInstanceOf(CommandExecutionError);
    });

    it('uploads validated local images through the discovered file input', async () => {
        const page = makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: true, missing: [] },
                { ok: true, selector: '[id="upload"]' },
                { ok: true },
                { status: 'published', item_id: '123456789012', url: 'https://www.goofish.com/item?id=123456789012' },
            ],
        });

        await publishCommand.func(page, { ...validArgs, images: 'a.png,b.webp' });

        expect(page.setFileInput).toHaveBeenCalledWith(['/abs/a.png', '/abs/b.webp'], '[id="upload"]');
    });

    it('does not return a success row for failed or unconfirmed publish states', async () => {
        await expect(publishCommand.func(makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: true, missing: [] },
                { ok: true },
                { status: 'failed', message: '内容违规' },
            ],
        }), validArgs)).rejects.toBeInstanceOf(CommandExecutionError);

        await expect(publishCommand.func(makePage({
            evaluateResults: [
                { hasPublishForm: true },
                { ok: true },
                { ok: true, missing: [] },
                { ok: true },
            ],
        }), validArgs)).rejects.toBeInstanceOf(CommandExecutionError);
    });

    it('browser category script is async and returns typed failure reasons', async () => {
        const result = await runBrowserScript('<button>其他</button>', __test__.buildSelectCategoryEvaluate('笔记本'));

        expect(result).toEqual({ ok: false, reason: 'category-trigger-not-found' });
    });

    it('browser fill script reports missing required fields', async () => {
        const result = await runBrowserScript(`

              <input placeholder="标题" />
              <textarea id="desc"></textarea>
              <button>轻微使用</button>

        `, __test__.buildFillFormEvaluate(validArgs));

        expect(result.ok).toBe(false);
        expect(result.missing).toContain('price');
    });

    it('browser success detector distinguishes success from failure and unknown states', async () => {
        await expect(runBrowserScript('发布成功', __test__.buildDetectSuccessEvaluate(), {
            url: 'https://www.goofish.com/item?id=123456789012',
        })).resolves.toMatchObject({ status: 'published', item_id: '123456789012' });

        await expect(runBrowserScript('
内容违规
', __test__.buildDetectSuccessEvaluate()))
            .resolves.toMatchObject({ status: 'failed', message: '内容违规' });

        await expect(runBrowserScript('处理中', __test__.buildDetectSuccessEvaluate()))
            .resolves.toEqual({ ok: false, reason: 'unknown-state' });
    });
});


/*
 * Copyright 2024 Netflix, Inc.
 *
 * 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.
 */
package com.netflix.maestro.models.definition;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
 * Subworkflow step definition with additional fields.
 *
 * <p>Note that there is no step retry for subworkflow step. It might fail due to various reasons,
 * e.g. invalid SEL expression evaluation, etc. But retries won't help in those cases.
 */
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder(
    value = {
      "id",
      "name",
      "description",
      "transition",
      "sync",
      "explicit_params",
      "failure_mode",
      "tags",
      "timeout",
      "signal_dependencies",
      "signal_outputs",
      "params"
    },
    alphabetic = true)
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public final class SubworkflowStep extends AbstractStep {
  private Boolean sync; // decide if the step waiting for the subworkflow instance to complete
  private Boolean explicitParams; // control if passing down the workflow params to the subworkflow

  @JsonIgnore
  @Override
  public StepType getType() {
    return StepType.SUBWORKFLOW;
  }
}


const fs = require('fs');
const path = require('path');
const os = require('os');
const { getClaudeDir, getConfigDir } = require('./ponytail-config');

const STATE_FILE = '.ponytail-active';
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
const isQoder = !isCopilot && !isCodex && Boolean(process.env.QODER_SESSION_ID);

let stateDir = getClaudeDir();
if (isCodex) stateDir = process.env.PLUGIN_DATA;
if (isCopilot) stateDir = process.env.COPILOT_PLUGIN_DATA;
if (isQoder) stateDir = path.join(os.homedir(), '.qoder');

const statePath = path.join(stateDir, STATE_FILE);

function setMode(mode) {
  fs.mkdirSync(path.dirname(statePath), { recursive: true });
  fs.writeFileSync(statePath, mode);
}

function clearMode() {
  try { fs.unlinkSync(statePath); } catch (e) {}
}

// Live mode written by activate/mode-tracker. Absent flag = ponytail off.
function readMode() {
  try {
    return fs.readFileSync(statePath, 'utf8').trim() || null;
  } catch (e) {
    return null;
  }
}

function writeHookOutput(event, mode, context = '') {
  if (isCopilot) {
    // Copilot reads additionalContext on SessionStart; ignores output elsewhere.
    process.stdout.write(JSON.stringify(
      event === 'SessionStart' && context ? { additionalContext: context } : {}));
    return;
  }
  if (isCodex) {
    const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
    if (context) {
      output.hookSpecificOutput = {
        hookEventName: event,
        additionalContext: context,
      };
    }
    process.stdout.write(JSON.stringify(output));
    return;
  }
  if (isQoder) {
    // Qoder: hookSpecificOutput JSON, same shape as Codex minus systemMessage.
    // UserPromptSubmit additionalContext is injected into the Agent's conversation.
    const output = {};
    if (context) {
      output.hookSpecificOutput = {
        hookEventName: event,
        additionalContext: context,
      };
    }
    process.stdout.write(JSON.stringify(output));
    return;
  }
  // Native Claude: SessionStart accepts raw stdout, but SubagentStart needs the
  // hookSpecificOutput JSON form or the context is dropped.
  if (event === 'SubagentStart') {
    process.stdout.write(JSON.stringify(
      { hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
    return;
  }
  process.stdout.write(context);
}

module.exports = {
  clearMode,
  isCodex,
  isCopilot,
  isQoder,
  readMode,
  setMode,
  writeHookOutput,
};


import pc from "picocolors";
import { checkbox, type Separator } from "@inquirer/prompts";
import readline from "readline";

type CheckboxConfig<T> = Parameters<typeof checkbox<T>>[0];
type CheckboxChoice<T> = Exclude<CheckboxConfig<T>["choices"][number], Separator | string>;

/**
 * Creates a clickable terminal hyperlink using OSC 8 escape sequence.
 */
export function terminalLink(text: string, url: string, color?: (s: string) => string): string {
  const colorFn = color ?? ((s: string) => s);
  return `\x1b]8;;${url}\x07${colorFn(text)}\x1b]8;;\x07 ${pc.white("↗")}`;
}

/**
 * Formats install count into a popularity star rating (4 stars).
 * 0/unknown → ☆☆☆☆, <100 → ★☆☆☆, <500 → ★★☆☆, <1000 → ★★★☆, 1000+ → ★★★★
 */
export function formatPopularity(count: number | undefined): string {
  const filled = "★";
  const empty = "☆";
  const max = 4;
  let stars: number;
  if (count === undefined || count === 0) stars = 0;
  else if (count < 100) stars = 1;
  else if (count < 500) stars = 2;
  else if (count < 1000) stars = 3;
  else stars = 4;

  const filledPart = filled.repeat(stars);
  const emptyPart = empty.repeat(max - stars);
  if (stars === 0) return pc.dim(emptyPart);
  return pc.yellow(filledPart) + pc.dim(emptyPart);
}

/**
 * Returns the install count as a human-readable range string.
 */
export function formatInstallRange(count: number | undefined): string {
  if (count === undefined || count === 0) return "Unknown";
  if (count < 100) return "<100";
  if (count < 500) return "<500";
  if (count < 1000) return "<1,000";
  return "1,000+";
}

/**
 * Formats trust score as High / Medium / Low label.
 * Uses MCP reputation thresholds: >=7 High, >=4 Medium, <4 Low.
 */
export function formatTrust(score: number | undefined): string {
  if (score === undefined || score < 0) return pc.dim("-");
  if (score >= 7) return pc.green("High");
  if (score >= 4) return pc.yellow("Medium");
  return pc.red("Low");
}

/**
 * Returns the raw trust label string (uncolored) for width calculations.
 */
export function getTrustLabel(score: number | undefined): string {
  if (score === undefined || score < 0) return "-";
  if (score >= 7) return "High";
  if (score >= 4) return "Medium";
  return "Low";
}
export interface CheckboxWithHoverOptions<T> {
  /** Function to extract display name from value. Defaults to (v) => v.name */
  getName?: (value: T) => string;
}

export async function checkboxWithHover<T>(
  config: CheckboxConfig<T>,
  options?: CheckboxWithHoverOptions<T>
): Promise<T[]> {
  const choices = config.choices.filter(
    (c): c is CheckboxChoice<T> =>
      typeof c === "object" && c !== null && !("type" in c && c.type === "separator")
  );
  const values = choices.map((c) => c.value);
  const totalItems = values.length;
  let cursorPosition = choices.findIndex((c) => !c.disabled);
  if (cursorPosition < 0) cursorPosition = 0;

  // Default getName assumes object has 'name' property
  const getName = options?.getName ?? ((v: T) => (v as { name: string }).name);

  const keypressHandler = (_str: string | undefined, key: readline.Key) => {
    if (key.name === "up") {
      let next = cursorPosition - 1;
      while (next >= 0 && choices[next].disabled) next--;
      if (next >= 0) cursorPosition = next;
    } else if (key.name === "down") {
      let next = cursorPosition + 1;
      while (next < totalItems && choices[next].disabled) next++;
      if (next < totalItems) cursorPosition = next;
    }
  };

  readline.emitKeypressEvents(process.stdin);
  process.stdin.on("keypress", keypressHandler);

  const customConfig = {
    ...config,
    theme: {
      ...config.theme,
      style: {
        answer: (text: string) => pc.green(text),
        ...config.theme?.style,
        highlight: (text: string) => pc.green(text),
        renderSelectedChoices: (
          selected: CheckboxChoice<T>[],
          _allChoices: CheckboxChoice<T>[]
        ): string => {
          if (selected.length === 0) {
            return pc.dim(getName(values[cursorPosition]));
          }
          return selected.map((c) => getName(c.value)).join(", ");
        },
      },
    },
  };

  try {
    const selected = await checkbox(customConfig);
    if (selected.length === 0) {
      return [values[cursorPosition]];
    }
    return selected;
  } finally {
    process.stdin.removeListener("keypress", keypressHandler);
  }
}


package io.unitycatalog.server.security;

import com.auth0.jwt.algorithms.Algorithm;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Security settings for the Authnz framework.
 *
 * <p>The settings are loaded from files persisted in the UC configuration folder. If no settings
 * files exist, they will be generated for reuse across restarts. Equivalent openssl commands are -
 * openssl genrsa -out private_key.pem 2048 - openssl rsa -in private_key.pem -pubout -outform DER
 * -out public_key.der - openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out
 * private_key.der -nocrypt - openssl rand -hex -out key_id.txt 32
 */
public class SecurityConfiguration {

  private static final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);

  private Path rsa512PublicKey;
  private Path rsa512PrivateKey;
  private Path keyId;

  @SneakyThrows
  public SecurityConfiguration(Path configurationFolder) {
    rsa512PublicKey = configurationFolder.resolve("public_key.der");
    rsa512PrivateKey = configurationFolder.resolve("private_key.der");
    keyId = configurationFolder.resolve("key_id.txt");

    initializeIfMissing();
  }

  @SneakyThrows
  public void initializeIfMissing() {
    if (Files.notExists(rsa512PublicKey)
        || Files.notExists(rsa512PrivateKey)
        || Files.notExists(keyId)) {
      log.info("Initializing security configuration.");
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      KeyPair keyPair = keyPairGenerator.generateKeyPair();

      // Create parent directory first if it does not exist
      Path parentDirectory = rsa512PublicKey.getParent();
      if (parentDirectory != null && !Files.exists(parentDirectory)) {
        Files.createDirectories(parentDirectory);
      }
      Files.write(rsa512PublicKey, keyPair.getPublic().getEncoded(), StandardOpenOption.CREATE);
      Files.write(rsa512PrivateKey, keyPair.getPrivate().getEncoded(), StandardOpenOption.CREATE);

      byte[] keyIdBytes = new byte[32];
      new SecureRandom().nextBytes(keyIdBytes);
      Files.writeString(keyId, Hex.encodeHexString(keyIdBytes), StandardOpenOption.CREATE);
    }
  }

  public Algorithm algorithmRSA()
      throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    return Algorithm.RSA512(rsaPublicKey(), rsaPrivateKey());
  }

  public RSAPublicKey rsaPublicKey()
      throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    if (Files.notExists(rsa512PublicKey)) {
      log.info("No JWT public signing key present.");
      return null;
    }
    byte[] keyBytes = Files.readAllBytes(rsa512PublicKey);

    X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return (RSAPublicKey) kf.generatePublic(spec);
  }

  public RSAPrivateKey rsaPrivateKey()
      throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    if (Files.notExists(rsa512PrivateKey)) {
      log.info("No JWT private signing key present.");
      return null;
    }
    byte[] keyBytes = Files.readAllBytes(rsa512PrivateKey);

    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return (RSAPrivateKey) kf.generatePrivate(spec);
  }

  public String getKeyId() throws IOException {
    return Files.readString(keyId).trim();
  }
}


import { buildCodeFenceMask } from './requirement-text.js';

export interface RequirementBlock {
  headerLine: string; // e.g., '### Requirement: Something'
  name: string; // e.g., 'Something'
  raw: string; // full block including headerLine and following content
}

export interface RequirementsSectionParts {
  before: string;
  headerLine: string; // the '## Requirements' line
  preamble: string; // content between headerLine and first requirement block
  bodyBlocks: RequirementBlock[]; // parsed requirement blocks in order
  after: string;
}

export function normalizeRequirementName(name: string): string {
  return name.trim();
}

/**
 * Case- and whitespace-insensitive fold of a requirement name. Requirement
 * matching itself is case-sensitive (normalizeRequirementName); this fold
 * exists only for typo detection - near-miss REMOVED headers and the
 * RENAMED+REMOVED cross-section conflict - where two spellings that differ
 * only in case or interior whitespace mean a mistake, never two requirements.
 */
export function foldRequirementName(name: string): string {
  return normalizeRequirementName(name).toLowerCase().replace(/\s+/g, ' ');
}

/** The canonical requirement header the delta reader recognizes. */
const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;

/**
 * Extracts the Requirements section from a spec file and parses requirement blocks.
 */
export function extractRequirementsSection(content: string): RequirementsSectionParts {
  const normalized = normalizeLineEndings(content);
  const lines = normalized.split('\n');
  const fenceMask = buildCodeFenceMask(lines);
  const reqHeaderIndex = lines.findIndex((l, i) => !fenceMask[i] && /^##\s+Requirements\s*$/i.test(l));

  if (reqHeaderIndex === -1) {
    // No requirements section; create an empty one at the end
    const before = content.trimEnd();
    const headerLine = '## Requirements';
    return {
      before: before ? before + '\n\n' : '',
      headerLine,
      preamble: '',
      bodyBlocks: [],
      after: '\n',
    };
  }

  // Find end of this section: next line that starts with '## ' at same or higher level
  let endIndex = lines.length;
  for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
    if (!fenceMask[i] && /^##\s+/.test(lines[i])) {
      endIndex = i;
      break;
    }
  }

  const before = lines.slice(0, reqHeaderIndex).join('\n');
  const headerLine = lines[reqHeaderIndex];
  const sectionBodyLines = lines.slice(reqHeaderIndex + 1, endIndex);
  const sectionBodyMask = fenceMask.slice(reqHeaderIndex + 1, endIndex);
  const isRequirementHeader = (cursor: number): boolean =>
    !sectionBodyMask[cursor] && REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor]);
  const isTopLevelHeader = (cursor: number): boolean =>
    !sectionBodyMask[cursor] && /^##\s+/.test(sectionBodyLines[cursor]);

  // Parse requirement blocks within section body
  const blocks: RequirementBlock[] = [];
  let cursor = 0;
  let preambleLines: string[] = [];

  // Collect preamble lines until first requirement header
  while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor)) {
    preambleLines.push(sectionBodyLines[cursor]);
    cursor++;
  }

  while (cursor < sectionBodyLines.length) {
    const headerLineCandidate = sectionBodyLines[cursor];
    if (!isRequirementHeader(cursor)) {
      // Not a requirement header; skip line defensively
      cursor++;
      continue;
    }
    const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX)!;
    const name = normalizeRequirementName(headerMatch[1]);
    cursor++;
    // Gather lines until next requirement header or end of section
    const bodyLines: string[] = [headerLineCandidate];
    while (cursor < sectionBodyLines.length && !isRequirementHeader(cursor) && !isTopLevelHeader(cursor)) {
      bodyLines.push(sectionBodyLines[cursor]);
      cursor++;
    }
    const raw = bodyLines.join('\n').trimEnd();
    blocks.push({ headerLine: headerLineCandidate, name, raw });
  }

  const after = lines.slice(endIndex).join('\n');
  const preamble = preambleLines.join('\n').trimEnd();

  return {
    before: before.trimEnd() ? before + '\n' : before,
    headerLine,
    preamble,
    bodyBlocks: blocks,
    after: after.startsWith('\n') ? after : '\n' + after,
  };
}

/**
 * A level-3 header inside `## ADDED`/`## MODIFIED Requirements` that is not a
 * canonical `### Requirement:` header, recorded at the moment the delta reader
 * skips over it. Surfaced as an INFO note by `validate <change>` (#498).
 */
export interface SkippedHeader {
  header: string; // header text without the leading ###
  section: string; // the ## section title as written
  line: number; // 1-based line number in the delta file
}

export interface DeltaPlan {
  added: RequirementBlock[];
  modified: RequirementBlock[];
  removed: string[]; // requirement names
  renamed: Array<{ from: string; to: string }>;
  skippedHeaders: SkippedHeader[]; // non-canonical ### headers the reader skipped
  sectionPresence: {
    added: boolean;
    modified: boolean;
    removed: boolean;
    renamed: boolean;
  };
}

function normalizeLineEndings(content: string): string {
  return content.replace(/\r\n?/g, '\n');
}

/**
 * A slice of a document represented as its lines plus a parallel mask marking
 * lines that live inside fenced code blocks (which must be ignored when
 * detecting Markdown structure).
 */
interface SectionBody {
  lines: string[];
  fenceMask: boolean[];
  bodyStartLine: number;
}

/**
 * Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
 */
export function parseDeltaSpec(content: string): DeltaPlan {
  const normalized = normalizeLineEndings(content);
  const lines = normalized.split('\n');
  const fenceMask = buildCodeFenceMask(lines);
  const sections = splitTopLevelSections(lines, fenceMask);
  const addedLookup = getSectionCaseInsensitive(sections, 'ADDED Requirements');
  const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
  const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
  const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
  const skippedHeaders: SkippedHeader[] = [];
  const added = parseRequirementBlocksFromSection(addedLookup.body, {
    section: addedLookup.title,
    bodyStartLine: addedLookup.bodyStartLine,
    sink: skippedHeaders,
  });
  const modified = parseRequirementBlocksFromSection(modifiedLookup.body, {
    section: modifiedLookup.title,
    bodyStartLine: modifiedLookup.bodyStartLine,
    sink: skippedHeaders,
  });
  const removedNames = parseRemovedNames(removedLookup.body);
  const renamedPairs = parseRenamedPairs(renamedLookup.body);
  skippedHeaders.sort((a, b) => a.line - b.line);
  return {
    added,
    modified,
    removed: removedNames,
    renamed: renamedPairs,
    skippedHeaders,
    sectionPresence: {
      added: addedLookup.found,
      modified: modifiedLookup.found,
      removed: removedLookup.found,
      renamed: renamedLookup.found,
    },
  };
}

function splitTopLevelSections(lines: string[], fenceMask: boolean[]): Record<string, SectionBody> {
  const result: Record<string, SectionBody> = {};
  const indices: Array<{ title: string; index: number }> = [];
  for (let i = 0; i < lines.length; i++) {
    if (fenceMask[i]) continue;
    const m = lines[i].match(/^(##)\s+(.+)$/);
    if (m) {
      indices.push({ title: m[2].trim(), index: i });
    }
  }
  for (let i = 0; i < indices.length; i++) {
    const current = indices[i];
    const next = indices[i + 1];
    const end = next ? next.index : lines.length;
    result[current.title] = {
      lines: lines.slice(current.index + 1, end),
      fenceMask: fenceMask.slice(current.index + 1, end),
      bodyStartLine: current.index + 2,
    };
  }
  return result;
}

const EMPTY_SECTION_BODY: SectionBody = { lines: [], fenceMask: [], bodyStartLine: 0 };

function getSectionCaseInsensitive(
  sections: Record<string, SectionBody>,
  desired: string
): { title: string; body: SectionBody; bodyStartLine: number; found: boolean } {
  const target = desired.toLowerCase();
  for (const [title, body] of Object.entries(sections)) {
    if (title.toLowerCase() === target) {
      return { title, body, bodyStartLine: body.bodyStartLine, found: true };
    }
  }
  return { title: desired, body: EMPTY_SECTION_BODY, bodyStartLine: 0, found: false };
}

function parseRequirementBlocksFromSection(
  sectionBody: SectionBody,
  skipped?: { section: string; bodyStartLine: number; sink: SkippedHeader[] }
): RequirementBlock[] {
  const { lines, fenceMask } = sectionBody;
  if (lines.length === 0) return [];
  const isRequirementHeader = (i: number): boolean => !fenceMask[i] && REQUIREMENT_HEADER_REGEX.test(lines[i]);
  const isTopLevelHeader = (i: number): boolean => !fenceMask[i] && /^##\s+/.test(lines[i]);
  const recordIfSkippedHeader = (index: number) => {
    if (!skipped || fenceMask[index]) return;
    const h3 = lines[index].match(/^###\s+(.+?)\s*$/);
    if (h3 && !REQUIREMENT_HEADER_REGEX.test(lines[index])) {
      skipped.sink.push({
        header: h3[1].trim(),
        section: skipped.section,
        line: skipped.bodyStartLine + index,
      });
    }
  };
  const blocks: RequirementBlock[] = [];
  let i = 0;
  while (i < lines.length) {
    // Seek next requirement header
    while (i < lines.length && !isRequirementHeader(i)) {
      recordIfSkippedHeader(i);
      i++;
    }
    if (i >= lines.length) break;
    const headerLine = lines[i];
    const m = headerLine.match(REQUIREMENT_HEADER_REGEX);
    if (!m) { i++; continue; }
    const name = normalizeRequirementName(m[1]);
    const buf: string[] = [headerLine];
    i++;
    while (i < lines.length && !isRequirementHeader(i) && !isTopLevelHeader(i)) {
      recordIfSkippedHeader(i);
      buf.push(lines[i]);
      i++;
    }
    blocks.push({ headerLine, name, raw: buf.join('\n').trimEnd() });
  }
  return blocks;
}

function parseRemovedNames(sectionBody: SectionBody): string[] {
  const { lines, fenceMask } = sectionBody;
  if (lines.length === 0) return [];
  const names: string[] = [];
  for (let i = 0; i < lines.length; i++) {
    if (fenceMask[i]) continue;
    const line = lines[i];
    const m = line.match(REQUIREMENT_HEADER_REGEX);
    if (m) {
      names.push(normalizeRequirementName(m[1]));
      continue;
    }
    // Also support bullet list of headers
    const bullet = line.match(/^\s*-\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
    if (bullet) {
      names.push(normalizeRequirementName(bullet[1]));
    }
  }
  return names;
}

function parseRenamedPairs(sectionBody: SectionBody): Array<{ from: string; to: string }> {
  const { lines, fenceMask } = sectionBody;
  if (lines.length === 0) return [];
  const pairs: Array<{ from: string; to: string }> = [];
  let current: { from?: string; to?: string } = {};
  for (let i = 0; i < lines.length; i++) {
    if (fenceMask[i]) continue;
    const line = lines[i];
    const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
    const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
    if (fromMatch) {
      current.from = normalizeRequirementName(fromMatch[1]);
    } else if (toMatch) {
      current.to = normalizeRequirementName(toMatch[1]);
      if (current.from && current.to) {
        pairs.push({ from: current.from, to: current.to });
        current = {};
      }
    }
  }
  return pairs;
}


#!/usr/bin/env node
// ponytail — Claude Code SubagentStart hook
//
// SessionStart context is parent-thread only and never reaches subagents, so
// without this every Task-spawned agent runs ponytail-unaware (issue #252).
// When ponytail mode is active, inject the same ruleset into each subagent.
//
// Scoping (opt-in, issue #506): set PONYTAIL_SUBAGENT_MATCHER to a regex and
// the ruleset is injected only into subagents whose agent_type matches. The
// regex is unanchored and case-insensitive — "explore|general" matches either,
// "^general$" is exact. Unset means inject into every subagent, as before.

const { getPonytailInstructions } = require('./ponytail-instructions');
const { readMode, writeHookOutput } = require('./ponytail-runtime');

const mode = readMode();

// Absent flag or off → ponytail isn't active; inject nothing.
if (!mode || mode === 'off') {
  process.exit(0);
}

function inject() {
  try {
    writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
  } catch (e) {
    // Silent fail — a stdout error at hook exit must not surface as a hook failure.
  }
}

// A bad regex must never crash the hook; treat it as "no matcher" and inject.
let matcherRe = null;
try {
  if (process.env.PONYTAIL_SUBAGENT_MATCHER) {
    matcherRe = new RegExp(process.env.PONYTAIL_SUBAGENT_MATCHER, 'i');
  }
} catch (e) {
  matcherRe = null;
}

// No matcher → keep the original synchronous, stdin-independent path. On Windows
// the PowerShell `if {}` wrapper can swallow the piped JSON so stdin 'end' never
// fires (#443); the default path must not wait on stdin or it would stall every
// subagent spawn.
if (!matcherRe) {
  inject();
  process.exit(0);
}

// Matcher set → read agent_type from stdin and skip only on a definite
// mismatch. Missing/unparseable agent_type, a stdin error, or the timeout all
// fail open (inject), so scoping never silently drops the persona.
let input = '';
let done = false;

function finish() {
  if (done) return;
  done = true;

  let agentType = '';
  try {
    // Strip UTF-8 BOM some shells prepend when piping (breaks JSON.parse)
    agentType = String(JSON.parse(input.replace(/^\uFEFF/, '')).agent_type || '').trim();
  } catch (e) {
    // Unparseable payload — fall through and inject to be safe.
  }
  if (agentType && !matcherRe.test(agentType)) {
    process.exit(0);
  }
  inject();
}

process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', finish);
// Never block the session (#443): recover on stdin error or a short fallback.
process.stdin.on('error', () => { finish(); process.exit(0); });
setTimeout(() => { finish(); process.exit(0); }, 1000).unref();


/*
===============================================================================
DDL Script: Create Gold Views
===============================================================================
Script Purpose:
    This script creates views for the Gold layer in the data warehouse.
    The Gold layer represents the final dimension and fact tables (Star Schema)

    Each view performs transformations and combines data from the Silver layer
    to produce a clean, enriched, and business-ready dataset.

Usage:
    - These views can be queried directly for analytics and reporting.
===============================================================================
*/

-- =============================================================================
-- Create Dimension: gold.dim_customers
-- =============================================================================
IF OBJECT_ID('gold.dim_customers', 'V') IS NOT NULL
    DROP VIEW gold.dim_customers;
GO

CREATE VIEW gold.dim_customers AS
SELECT
    ROW_NUMBER() OVER (ORDER BY cst_id) AS customer_key, -- Surrogate key
    ci.cst_id                          AS customer_id,
    ci.cst_key                         AS customer_number,
    ci.cst_firstname                   AS first_name,
    ci.cst_lastname                    AS last_name,
    la.cntry                           AS country,
    ci.cst_marital_status              AS marital_status,
    CASE
        WHEN ci.cst_gndr != 'n/a' THEN ci.cst_gndr -- CRM is the primary source for gender
        ELSE COALESCE(ca.gen, 'n/a')  			   -- Fallback to ERP data
    END                                AS gender,
    ca.bdate                           AS birthdate,
    ci.cst_create_date                 AS create_date
FROM silver.crm_cust_info ci
LEFT JOIN silver.erp_cust_az12 ca
    ON ci.cst_key = ca.cid
LEFT JOIN silver.erp_loc_a101 la
    ON ci.cst_key = la.cid;
GO

-- =============================================================================
-- Create Dimension: gold.dim_products
-- =============================================================================
IF OBJECT_ID('gold.dim_products', 'V') IS NOT NULL
    DROP VIEW gold.dim_products;
GO

CREATE VIEW gold.dim_products AS
SELECT
    ROW_NUMBER() OVER (ORDER BY pn.prd_start_dt, pn.prd_key) AS product_key, -- Surrogate key
    pn.prd_id       AS product_id,
    pn.prd_key      AS product_number,
    pn.prd_nm       AS product_name,
    pn.cat_id       AS category_id,
    pc.cat          AS category,
    pc.subcat       AS subcategory,
    pc.maintenance  AS maintenance,
    pn.prd_cost     AS cost,
    pn.prd_line     AS product_line,
    pn.prd_start_dt AS start_date
FROM silver.crm_prd_info pn
LEFT JOIN silver.erp_px_cat_g1v2 pc
    ON pn.cat_id = pc.id
WHERE pn.prd_end_dt IS NULL; -- Filter out all historical data
GO

-- =============================================================================
-- Create Fact Table: gold.fact_sales
-- =============================================================================
IF OBJECT_ID('gold.fact_sales', 'V') IS NOT NULL
    DROP VIEW gold.fact_sales;
GO

CREATE VIEW gold.fact_sales AS
SELECT
    sd.sls_ord_num  AS order_number,
    pr.product_key  AS product_key,
    cu.customer_key AS customer_key,
    sd.sls_order_dt AS order_date,
    sd.sls_ship_dt  AS shipping_date,
    sd.sls_due_dt   AS due_date,
    sd.sls_sales    AS sales_amount,
    sd.sls_quantity AS quantity,
    sd.sls_price    AS price
FROM silver.crm_sales_details sd
LEFT JOIN gold.dim_products pr
    ON sd.sls_prd_key = pr.product_number
LEFT JOIN gold.dim_customers cu
    ON sd.sls_cust_id = cu.customer_id;
GO


#!/usr/bin/env bash
set -Eeuo pipefail

getPlatform() {

  local xml="$1"
  local platform="x64"
  local x86 x64 arm64 count=0

  x86=$(xmllint --nonet --xpath 'count(/WIM/IMAGE/WINDOWS/ARCH[text()="0"])' - 2>/dev/null <<< "$xml") || x86=0
  x64=$(xmllint --nonet --xpath 'count(/WIM/IMAGE/WINDOWS/ARCH[text()="9"])' - 2>/dev/null <<< "$xml") || x64=0
  arm64=$(xmllint --nonet --xpath 'count(/WIM/IMAGE/WINDOWS/ARCH[text()="12"])' - 2>/dev/null <<< "$xml") || arm64=0

  (( x86 > 0 )) && ((count++))
  (( x64 > 0 )) && ((count++))
  (( arm64 > 0 )) && ((count++))

  if (( count > 1 )); then
    platform="mixed"
  elif (( x86 > 0 )); then
    platform="x86"
  elif (( arm64 > 0 )); then
    platform="arm64"
  fi

  echo "$platform"
  return 0
}

checkPlatform() {

  local xml="$1"
  local platform compat

  platform=$(getPlatform "$xml")

  case "${platform,,}" in
    "x86" ) compat="x64" ;;
    "x64" ) compat="$platform" ;;
    "arm64" ) compat="$platform" ;;
    "mixed" )
      error "Windows images with mixed architectures are not supported!"
      return 1
      ;;
    * ) compat="${PLATFORM,,}" ;;
  esac

  [[ "${compat,,}" == "${PLATFORM,,}" ]] && return 0

  error "You cannot boot ${platform^^} images on a $PLATFORM CPU!"
  return 1
}

hasVersion() {

  local wanted="$1"
  shift

  local actual

  for actual in "$@"; do
    [[ "${actual,,}" == "${wanted,,}" ]] || continue
    echo "$actual"
    return 0
  done

  return 1
}

getCompatibleVersions() {

  local wanted="$1"
  local result_name="$2"
  local -n result_ref="$result_name"

  result_ref=("$wanted")

  # Treat normal and Evaluation variants of the same edition as compatible.
  # The exact requested variant is always checked first.
  if [[ "${wanted,,}" == *"-eval" ]]; then
    result_ref+=("${wanted%-eval}")
  else
    result_ref+=("$wanted-eval")
  fi
}

hasAnswerFile() {

  local id="$1"
  local file="/run/assets/$id.xml"

  [ -s "$file" ] && return 0

  if [[ "${id,,}" == *"-eval" ]]; then
    file="/run/assets/${id%-eval}.xml"
    [ -s "$file" ] && return 0
  fi

  # Editions without a dedicated template can use the generic template.
  case "${id,,}" in
    "win7"* | "win8"* | "win10"* | "win11"* | "winvista"* | "win20"* )
      file="/run/assets/${id%%-*}.xml"
      [ -s "$file" ] && return 0
      ;;
  esac

  return 1
}

getVersionPriority() {

  local id="${1,,}"
  local base="${2,,}"
  local order_name="EDITION_ORDER"
  local entry priority patterns pattern
  local result="other" score best_score=-1

  id="${id%-eval}"

  case "$base" in
    "win20"* )
      order_name="SERVER_EDITION_ORDER"
      ;;
  esac

  local -n order_ref="$order_name"

  local edition="${id#"$base"}"
  edition="${edition#-}"

  # Use the most specific matching pattern. This prevents broad patterns
  # such as enterprise-* from taking precedence over enterprise-iot-*.
  for entry in "${order_ref[@]}"; do

    IFS='|' read -r _ priority patterns <<< "$entry"

    for pattern in $patterns; do

      if [ "$pattern" = "@default" ]; then
        [ -z "$edition" ] || continue
        score=1
      elif [[ "$pattern" == *"*" ]]; then
        local prefix="${pattern%\*}"
        [[ "$edition" == "$prefix"* ]] || continue
        score="${#pattern}"
      elif [ "$edition" = "$pattern" ]; then
        score="${#pattern}"
      else
        continue
      fi

      if (( score > best_score )); then
        result="$priority"
        best_score="$score"
      fi

    done

  done

  echo "$result"
  return 0
}

getVersions() {

  local xml="$1"
  local versions_name="$2"
  local bases_name="$3"
  local groups_name="$4"
  local indexes_name="$5"
  local -n versions_ref="$versions_name"
  local -n bases_ref="$bases_name"
  local -n groups_ref="$groups_name"
  local -n indexes_ref="$indexes_name"

  local count image image_index
  local display product platform
  local edition_id install_type
  local candidate flags i

  versions_ref=()
  bases_ref=()
  groups_ref=()
  indexes_ref=()

  platform=$(getPlatform "$xml")
  count=$(xmllint --nonet --xpath 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 0

  for ((i=1; i<=count; i++)); do

    image_index=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/@INDEX)" - 2>/dev/null <<< "$xml") || continue
    display=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/DISPLAYNAME)" - 2>/dev/null <<< "$xml") || display=""
    product=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/WINDOWS/PRODUCTNAME)" - 2>/dev/null <<< "$xml") || product=""
    image=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/NAME)" - 2>/dev/null <<< "$xml") || image=""
    edition_id=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/WINDOWS/EDITIONID)" - 2>/dev/null <<< "$xml") || edition_id=""
    install_type=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/WINDOWS/INSTALLATIONTYPE)" - 2>/dev/null <<< "$xml") || install_type=""
    flags=$(xmllint --nonet --xpath "string(/WIM/IMAGE[$i]/FLAGS)" - 2>/dev/null <<< "$xml") || flags=""

    [ -n "$image_index" ] || continue
    local candidate_id=""
    local candidate_base=""

    # NAME normally contains the most precise edition identifier (including
    # Server Core), while DISPLAYNAME is the best fallback for other images.
    for candidate in "$image" "$display" "$product"; do

      [[ "$candidate" == *"Operating System"* ]] && continue
      [ -n "$candidate" ] || continue

      candidate_base=$(fromName "$candidate" "$platform")
      candidate_id=$(getVersion "$candidate" "$platform")

      [ -n "$candidate_base" ] && [ -n "$candidate_id" ] && break
    done

    if [ -z "$candidate_base" ] || [ -z "$candidate_id" ]; then
      local name="${display:-${image:-$product}}"
      [ -n "$name" ] && warn "Unknown image name: '$name'"
      continue
    fi

    local evaluation=""

    if [[ "${image,,}" == *"evaluation"* ||
      "${display,,}" == *"evaluation"* ||
      "${product,,}" == *"evaluation"* ||
      "${edition_id,,}" == *"eval"* ||
      "${flags,,}" == *"eval"* ]]; then
      evaluation="-eval"
    fi

    if [ -n "$evaluation" ] &&
      [[ "${candidate_id,,}" != *"-eval" ]]; then
      candidate_id+="$evaluation"
    fi

    local key="${candidate_id,,}"

    # Some client media use the same friendly name-derived ID for distinct
    # editions. Preserve the established unsuffixed Pro ID, and use the
    # structured edition metadata only to disambiguate a collision.
    if [[ -v "indexes_ref[$key]" ]]; then
      local structured=""

      case "${candidate_base,,}" in
        "winvista"* | "win7"* | "win8"* | "win10"* | "win11"* )
          structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base")
          ;;
        "win20"* )
          structured=$(normalizeServerEditionID "${flags:-$edition_id}")

          # Some media use the same EDITIONID for Core and Desktop images.
          # INSTALLATIONTYPE provides the structural distinction without
          # requiring a hardcoded marketing name.
          if [[ "${install_type,,}" == *"core"* &&
            "$structured" != *"-core" ]]; then
            structured+="-core"
          fi
          ;;
      esac

      if [ -n "$structured" ]; then
        candidate_id="$candidate_base-$structured$evaluation"
        key="${candidate_id,,}"
      fi
    fi

    if [[ -v "indexes_ref[$key]" ]]; then
      warn "Duplicate image identity '$candidate_id' at indexes ${indexes_ref[$key]} and $image_index"
      continue
    fi

    indexes_ref["$key"]="$image_index"
    versions_ref+=("$candidate_id")
    bases_ref+=("$candidate_base")
    groups_ref+=("$(getVersionPriority "$candidate_id" "$candidate_base")")

  done

  return 0
}

selectVersion() {

  local versions_name="$1"
  local indexes_name="$2"
  local preferred_name="$3"
  local result_name="$4"
  local index_name="$5"
  local -n version_list="$versions_name"
  local -n index_map="$indexes_name"
  local -n preference_list="$preferred_name"
  local -n selected_version="$result_name"
  local -n selected_image_index="$index_name"

  local wanted candidate match
  local -a candidates=()

  for wanted in "${preference_list[@]}"; do

    [ -n "$wanted" ] || continue
    getCompatibleVersions "$wanted" candidates

    for candidate in "${candidates[@]}"; do

      match=$(hasVersion "$candidate" "${version_list[@]}") || continue
      hasAnswerFile "$match" || continue

      local key="${match,,}"
      selected_version="$match"
      selected_image_index="${index_map[$key]}"
      return 0

    done

  done

  return 1
}

selectEdition() {

  local versions_name="$1"
  local bases_name="$2"
  local groups_name="$3"
  local indexes_name="$4"
  local suggested="$5"
  local result_name="$6"
  local index_name="$7"
  local normalize_name="$8"
  local order_name="$9"
  local -n edition_versions="$versions_name"
  local -n edition_bases="$bases_name"
  local -n edition_groups="$groups_name"
  local -n edition_order="$order_name"

  local base edition entry suffix priority i
  local -a preferred=()
  local -A seen=()

  if [ -n "$EDITION" ]; then

    for base in "${edition_bases[@]}"; do
      edition=$("$normalize_name" "$EDITION" "$base")
      preferred+=("$base${edition:+-$edition}")
    done

    if selectVersion \
        "$versions_name" \
        "$indexes_name" \
        preferred \
        "$result_name" \
        "$index_name"; then
      return 0
    fi

    warn "edition '$EDITION' is not supported by this image, using automatic selection instead."
  fi

  if [ -n "$suggested" ]; then

    preferred=("$suggested")

    if selectVersion \
        "$versions_name" \
        "$indexes_name" \
        preferred \
        "$result_name" \
        "$index_name"; then
      return 0
    fi

  fi

  # First try each canonical edition in its configured order.
  preferred=()

  for entry in "${edition_order[@]}"; do

    IFS='|' read -r suffix _ _ <<< "$entry"

    for base in "${edition_bases[@]}"; do
      preferred+=("$base$suffix")
    done

  done

  if selectVersion \
      "$versions_name" \
      "$indexes_name" \
      preferred \
      "$result_name" \
      "$index_name"; then
    return 0
  fi

  # Then try noncanonical editions from the same preference groups.
  preferred=()
  seen=()

  for entry in "${edition_order[@]}"; do

    IFS='|' read -r _ priority _ <<< "$entry"

    [[ -v "seen[$priority]" ]] && continue
    seen["$priority"]="Y"

    for ((i=0;i<${#edition_versions[@]};i++)); do
      [[ "${edition_groups[$i]}" == "$priority" ]] || continue
      preferred+=("${edition_versions[$i]}")
    done

  done

  selectVersion \
    "$versions_name" \
    "$indexes_name" \
    preferred \
    "$result_name" \
    "$index_name"
}

detectVersion() {

  local xml="$1"
  local suggested="${2:-}"
  local result_name="$3"
  local index_name="$4"

  local order_name="EDITION_ORDER"
  local normalize_name="normalizeEditionID"

  local -a bases=()
  local -a groups=()
  local -a versions=()
  local -A image_indexes=()

  printf -v "$result_name" '%s' ""
  printf -v "$index_name" '%s' ""

  getVersions \
    "$xml" \
    versions \
    bases \
    groups \
    image_indexes

  [ "${#versions[@]}" -eq 0 ] && return 0

  case "${bases[0],,}" in
    "win20"* )
      order_name="SERVER_EDITION_ORDER"
      normalize_name="normalizeServerEditionID"
      ;;
  esac

  selectEdition \
    versions \
    bases \
    groups \
    image_indexes \
    "$suggested" \
    "$result_name" \
    "$index_name" \
    "$normalize_name" \
    "$order_name" && return 0

  local result="${versions[0]}"
  local key="${result,,}"

  printf -v "$result_name" '%s' "$result"
  printf -v "$index_name" '%s' "${image_indexes[$key]}"

  return 0
}

detectLanguage() {

  local xml="$1"
  local index="${2:-}"
  local xpath lang

  if [[ "$index" =~ ^[0-9]+$ ]]; then
    xpath="string((/WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])"
  else
    xpath='string((/WIM/IMAGE/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])'
  fi

  lang=$(xmllint --nonet --xpath "$xpath" - 2>/dev/null <<< "$xml") || lang=""

  if [ -z "$lang" ]; then
    warn "Language could not be detected from ISO!"
    return 0
  fi

  local culture
  culture=$(getLanguage "$lang" "culture")
  [ -n "$culture" ] && LANGUAGE="$lang" && return 0

  warn "Invalid language detected: \"$lang\""
  return 0
}

skipVersion() {

  local id="$1"

  case "${id,,}" in
    "win9"* | "winxp"* | "win2k"* | "win2003"* )
      return 0 ;;
  esac

  return 1
}

detectLegacy() {

  local dir="$1"
  local find

  [[ "${PLATFORM,,}" != "x64" ]] && return 1

  find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit)
  [ -n "$find" ] && DETECTED="win95" && return 0

  find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit)
  [ -n "$find" ] && DETECTED="win98" && return 0

  find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit)
  [ -n "$find" ] && DETECTED="win9x" && return 0

  find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit)
  [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit)
  [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit)
  [ -n "$find" ] && DETECTED="winnt4" && return 0

  find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit)

  if [ -n "$find" ]; then

    find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_ID.5 -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IP.5 -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit)
    [ -n "$find" ] && DETECTED="win2k" && return 0

  fi

  find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit)

  if [ -n "$find" ]; then

    find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit)
    [ -n "$find" ] && DETECTED="winxpx64" && return 0

    find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IP -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname setupxp.htm -print -quit)
    [ -n "$find" ] && DETECTED="winxpx86" && return 0

    find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IL -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AA -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AD -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AS -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MA -print -quit)
    [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MD -print -quit)
    [ -n "$find" ] && DETECTED="win2003r2" && return 0

  fi

  return 1
}

resolveImage() {

  local version="$1"

  [ -z "$DETECTED" ] || return 0
  [ -z "$CUSTOM" ] || return 1
  [ -z "${REUSED_ISO:-}" ] || return 1
  [[ "${version,,}" != "http"* ]] || return 1

  local file="/run/assets/$version.xml"

  if [ -s "$file" ]; then
    DETECTED="$version"
    return 0
  fi

  if [[ "${version,,}" == *"-eval" ]]; then
    local source="/run/assets/${version%-eval}.xml"

    if [ -s "$source" ]; then
      DETECTED="$version"
      return 0
    fi
  fi

  return 1
}

setImage() {

  skipVersion "${DETECTED,,}" && return 0

  if ! setXML "" && ! enabled "$MANUAL"; then
    MANUAL="Y"

    local desc
    desc=$(printEdition "$DETECTED" "this version")
    warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
  fi

  return 0
}

findImage() {

  local dir="$1"
  local result_name="$2"

  local src result
  src=$(find "$dir" -maxdepth 1 -type d -iname sources -print -quit)

  if [ ! -d "$src" ]; then
    warn "failed to locate 'sources' folder in ISO image, $FB"
    return 1
  fi

  result=$(find "$src" -maxdepth 1 -type f \
    \( -iname install.wim -or -iname install.esd \) -print -quit)

  if [ ! -f "$result" ]; then
    warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB"
    return 1
  fi

  printf -v "$result_name" '%s' "$result"
  return 0
}

readImageInfo() {

  local wim="$1"
  local result_name="$2"
  local result

  result=$(wimlib-imagex info -xml "$wim" |
    iconv -f UTF-16LE -t UTF-8) || {
    local rc=$?

    if (( rc >= 129 )); then
      exit "$rc"
    fi

    warn "failed to read Windows image information, $FB"
    return 1
  }

  printf -v "$result_name" '%s' "$result"
  return 0
}

getSuggestion() {

  [ -z "$CUSTOM" ] || return 0
  [ -n "${REUSED_ISO:-}" ] || return 0

  echo "${SUGGEST:-}"
}

validateEdition() {

  [ -n "$EDITION" ] || return 0

  case "${DETECTED,,}" in
    "win20"* )
      local edition
      edition=$(normalizeServerEditionID "$EDITION")

      if [ -n "$edition" ] &&
        [[ "${DETECTED,,}" != *"-${edition,,}" &&
          "${DETECTED,,}" != *"-${edition,,}-eval" ]]; then
        EDITION=""
      fi
      ;;
  esac

  return 0
}

unknownImage() {

  local msg="Failed to determine Windows version from image"

  if setXML "" || enabled "$MANUAL"; then
    info "${msg}!"
  else
    MANUAL="Y"
    warn "${msg}, $FB."
  fi

  return 0
}

describeImage() {

  local info_xml="$1"
  local index="$2"
  local result_name="$3"
  local result

  result=$(printEdition "$DETECTED" "$DETECTED" "Y")

  detectLanguage "$info_xml" "$index"

  if [[ "${LANGUAGE,,}" != "en" && "${LANGUAGE,,}" != "en-"* ]]; then
    local language
    language=$(getLanguage "$LANGUAGE" "desc")
    result+=" ($language)"
  fi

  printf -v "$result_name" '%s' "$result"
  return 0
}

configureImage() {

  local index="$1"
  local desc="$2"

  setXML "" "$index" && return 0

  if [[ "$DETECTED" == "win81x86"* ||
    "$DETECTED" == "win10x86"* ]]; then
    error "The 32-bit version of $desc is not supported!"
    return 1
  fi

  local msg="the answer file for $desc was not found ($DETECTED.xml)"
  local fallback="/run/assets/${DETECTED%%-*}.xml"

  if setXML "$fallback" "$index" || enabled "$MANUAL"; then
    ! enabled "$MANUAL" && warn "${msg}."
  else
    MANUAL="Y"
    warn "${msg}, $FB."
  fi

  return 0
}

detectImage() {

  local dir="$1"
  local version="$2"
  local desc

  XML=""

  resolveImage "$version" || :

  if [ -n "$DETECTED" ]; then
    setImage || return 1
    return 0
  fi

  info "Detecting version from ISO image..."

  if detectLegacy "$dir"; then
    desc=$(printEdition "$DETECTED" "$DETECTED" "Y")
    info "Detected: $desc"
    return 0
  fi

  local wim
  findImage "$dir" wim || return 1

  local image_info
  readImageInfo "$wim" image_info || return 1

  checkPlatform "$image_info" || exit 67

  local suggested
  suggested=$(getSuggestion) || return 1

  local index
  detectVersion "$image_info" "$suggested" DETECTED index || return 1
  validateEdition || return 1

  if [ -z "$DETECTED" ]; then
    unknownImage || return 1
    return 0
  fi

  describeImage "$image_info" "$index" desc || return 1
  info "Detected: $desc"

  configureImage "$index" "$desc" || return 1

  return 0
}

normalizeBatch() {

  local file="$1"
  local bom tmp encoding

  [ ! -f "$file" ] && return 0
  [ ! -s "$file" ] && return 0

  bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1

  case "$bom" in
    "fffe" ) encoding="UTF-16LE" ;;
    "feff" ) encoding="UTF-16BE" ;;
    * ) return 0 ;;
  esac

  if ! tmp=$(mktemp "${file}.XXXXXX"); then
    error "Failed to create temporary batch file!"
    return 1
  fi

  if ! tail -c +3 "$file" | iconv -f "$encoding" -t UTF-8 > "$tmp"; then
    rm -f "$tmp"
    error "Failed to convert $file from $encoding to UTF-8!"
    return 1
  fi

  if ! chmod --reference="$file" "$tmp" || ! mv -f "$tmp" "$file"; then
    rm -f "$tmp"
    error "Failed to replace batch file: $file"
    return 1
  fi

  return 0
}

checkBatch() {

  local file="$1"
  local report="N"
  local tmp output
  local matches line

  [ -s "$file" ] || return 0

  if ! tmp=$(mktemp -d /tmp/blinter.XXXXXX); then
    warn "failed to create temporary Blinter directory."
    return 0
  fi

  local source="your install.bat file"
  [ -n "${COMMAND:-}" ] && source="your COMMAND variable"

  if enabled "$DEBUG"; then

    report="Y"

    if LC_ALL=C grep -Pq '[^\x09\x0D\x20-\x7E]' "$file"; then
      warn "non-ASCII characters were detected in $source and may not execute correctly in Windows Command Prompt."
    fi

  else

    # First pass: silently check only for Error-level findings.
    cat > "$tmp/blinter.ini" <<'EOC'
[general]
min_severity = error
EOC

    if ! (
      cd "$tmp"
      python3 -m blinter "$file" >/dev/null 2>&1
    ); then
      report="Y"
    fi

  fi

  if enabled "$report"; then

    # Show useful diagnostic context, while excluding findings that are
    # irrelevant to unattended OEM scripts.
    cat > "$tmp/blinter.ini" <<'EOC'
[general]
min_severity = warning
show_summary = false

[rules]
disabled_rules = W001,W028,W041,SEC002,SEC005
EOC

    output=$(
      cd "$tmp"
      python3 -m blinter "$file" 2>&1 || true
    )

    # Remove header
    output=$(
      awk '
        /^DETAILED ISSUES:/ {
          found = 1
          next
        }

        found && !started {
          if (/^-+$/) {
            started = 1
          }
          next
        }

        started {
          print
        }
      ' <<< "$output"
    )

    output="${output#"${output%%[!$'\r\n ']*}"}"
    output="${output%"${output##*[!$'\r\n ']}"}"

    if grep -Eq \
      '^(ERROR|WARNING|SECURITY) LEVEL ISSUES:$' \
      <<< "$output"; then

      warn "possible issues were detected in $source:"
      printf '\n%s\n\n' "$output" >&2
    fi

  fi

  rm -rf "$tmp"

  matches=$(
    grep -Pin \
      '(?<!\\)\\host[.]lan[\\]' \
      "$file" || true
  )

  if [ -n "$matches" ]; then
    warn "invalid single-backslash UNC path detected in $source:"

    while IFS= read -r line; do
      printf '  %s\n' "$line" >&2
    done <<< "$matches"

    printf '%s\n\n' \
      '  Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".' >&2
  fi

  matches=$(
    grep -Pin \
      '(?<![\\[:alnum:]._-])host[.]lan[\\]' \
      "$file" || true
  )

  if [ -n "$matches" ]; then
    warn "UNC path without leading backslashes detected in $source:"

    while IFS= read -r line; do
      printf '  %s\n' "$line" >&2
    done <<< "$matches"

    printf '%s\n\n' \
      '  Use "\\host.lan\Data\..." instead of "host.lan\Data\...".' >&2
  fi

  matches=$(
    grep -Pin \
      '//host[.]lan/' \
      "$file" || true
  )

  if [ -n "$matches" ]; then
    warn "invalid forward-slash UNC path detected in $source:"

    while IFS= read -r line; do
      printf '  %s\n' "$line" >&2
    done <<< "$matches"

    printf '%s\n\n' \
      '  Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".' >&2
  fi

  matches=$(
    grep -Pin \
      '\\\\host[.]lan\\shared(?:[\\/]|$)' \
      "$file" || true
  )

  if [ -n "$matches" ]; then
    warn "invalid Samba share name detected in $source:"

    while IFS= read -r line; do
      printf '  %s\n' "$line" >&2
    done <<< "$matches"

    printf '%s\n\n' \
      '  The "/shared" folder is exposed to Windows as "\\host.lan\Data".' >&2
  fi

  return 0
}

buildImage() {

  local dir="$1"
  local failed=""
  local cat="BOOT.CAT"
  local log="/run/shm/iso.log"
  local base size desc

  if [ -f "$BOOT" ]; then
    error "File $BOOT does already exist?!" && return 1
  fi

  base=$(basename "$BOOT")
  local out="$TMP/${base%.*}.tmp"
  rm -f "$out"

  desc=$(printVariant "$DETECTED" "ISO")

  local msg="Building $desc image"
  info "$msg..." && html "$msg..."

  [ -z "$LABEL" ] && LABEL="Windows"

  if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
    error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
  fi

  size=$(du -b --max-depth=0 "$dir" | cut -f1)
  checkFreeSpace "$TMP" "$size" || return 1

  /run/progress.sh "$out" "$size" "$msg ([P])..." &

  if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then

    genisoimage \
      -o "$out" \
      -b "$ETFS" \
      -no-emul-boot \
      -c "$cat" \
      -iso-level 4 \
      -J \
      -l \
      -D \
      -N \
      -joliet-long \
      -relaxed-filenames \
      -V "${LABEL::30}" \
      -udf \
      -boot-info-table \
      -eltorito-alt-boot \
      -eltorito-boot "$EFISYS" \
      -no-emul-boot \
      -allow-limited-size \
      -quiet \
      "$dir" 2> "$log" || failed="y"

  else

    case "${DETECTED,,}" in
      "win2k"* | "winxp"* | "win2003"* )
        genisoimage \
          -o "$out" \
          -b "$ETFS" \
          -no-emul-boot \
          -boot-load-seg 1984 \
          -boot-load-size 4 \
          -c "$cat" \
          -iso-level 2 \
          -J \
          -l \
          -D \
          -N \
          -joliet-long \
          -relaxed-filenames \
          -V "${LABEL::30}" \
          -quiet \
          "$dir" 2> "$log" || failed="y"
        ;;

      "win9"* )
        genisoimage \
          -o "$out" \
          -b "$ETFS" \
          -J \
          -r \
          -V "${LABEL::30}" \
          -quiet \
          "$dir" 2> "$log" || failed="y"
        ;;

      * )
        genisoimage \
          -o "$out" \
          -b "$ETFS" \
          -no-emul-boot \
          -boot-load-size 4 \
          -c "$cat" \
          -iso-level 2 \
          -J \
          -l \
          -D \
          -N \
          -joliet-long \
          -relaxed-filenames \
          -V "${LABEL::30}" \
          -udf \
          -allow-limited-size \
          -quiet \
          "$dir" 2> "$log" || failed="y"
        ;;
    esac

  fi

  fKill "progress.sh"

  if [ -n "$failed" ]; then
    [ -s "$log" ] && echo "$(<"$log")"
    error "Failed to build image!" && return 1
  fi

  local err=""
  local hide="Warning: creating filesystem that does not conform to ISO-9660."

  [ -s "$log" ] && err="$(<"$log")"
  [[ "$err" != "$hide" ]] && echo "$err"

  mv -f "$out" "$BOOT" || return 1

  if ! setOwner "$BOOT"; then
    warn "Failed to set the owner for \"$BOOT\" !"
  fi

  return 0
}

extractBootImage() {

  local iso="$1"
  local dir="$2"
  local desc="$3"

  local tmp="$TMP/boot-images"
  local max_size=$((32 * 1024 * 1024))
  local rc size len offset image=""
  local msg="using legacy extraction..."
  local -a images=()

  ETFS="boot.img"

  [ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] && return 0

  rm -f "$dir/$ETFS" || return 1
  rm -rf "$tmp" || return 1

  if LC_ALL=C xorriso \
      -no_rc \
      -osirrox on \
      -indev "$iso" \
      -extract_boot_images "$tmp" >/dev/null 2>&1; then

    mapfile -t images < <(
      find "$tmp" \
        -maxdepth 1 \
        -type f \
        -name 'eltorito_img*_bios.img' \
        -print
    )

    if (( ${#images[@]} == 1 )); then
      image="${images[0]}"

      if [ ! -s "$image" ]; then
        warn "The extracted BIOS boot image is empty, $msg"
      elif ! size=$(stat -c%s "$image"); then
        warn "Failed to determine the BIOS boot image size, $msg"
      elif (( size > max_size )); then
        warn "The extracted BIOS boot image exceeds 32 MB, $msg"
      else
        if ! mv -f "$image" "$dir/$ETFS"; then
          rm -rf "$tmp" || true
          error "Failed to save boot image from $desc ISO!"
          return 1
        fi

        rm -rf "$tmp" || return 1
        return 0
      fi

    elif (( ${#images[@]} > 1 )); then
      warn "Multiple BIOS boot images were found, $msg"
    else
      warn "No BIOS boot image was found, $msg"
    fi

  else
    rc=$?

    if (( rc > 128 )); then
      rm -rf "$tmp" || true
      exit "$rc"
    fi

    warn "Failed to extract the BIOS boot image, $msg"
  fi

  rm -rf "$tmp" || true

  if ! len=$(isoinfo -d -i "$iso" | grep "Nsect " | grep -o "[^ ]*$"); then
    error "Failed to determine boot image size from $desc ISO!"
    return 1
  fi

  if ! offset=$(isoinfo -d -i "$iso" | grep "Bootoff " | grep -o "[^ ]*$"); then
    error "Failed to determine boot image offset from $desc ISO!"
    return 1
  fi

  if [[ ! "$len" =~ ^[0-9]+$ ]] || [[ ! "$offset" =~ ^[0-9]+$ ]]; then
    error "Invalid boot image location found in $desc ISO!"
    return 1
  fi

  if ! dd \
      "if=$iso" \
      "of=$dir/$ETFS" \
      bs=2048 \
      "count=$len" \
      "skip=$offset" \
      status=none; then
    rm -f "$dir/$ETFS" || true
    error "Failed to extract boot image from $desc ISO!"
    return 1
  fi

  if [ ! -s "$dir/$ETFS" ]; then
    rm -f "$dir/$ETFS" || true
    error "Failed to locate file \"$ETFS\" in $desc ISO image!"
    return 1
  fi

  return 0
}

return 0