"""Plan command implementation.""" import json from pathlib import Path from typing import Optional import click from vlm.context import CLIContext from vlm.io import identities_to_plan_input, load_identities_json, load_analysis_json from vlm.planner import generate_plan, save_plan def plan_cmd(ctx: CLIContext, input: Path, output: Path, analysis: Optional[Path] = None) -> None: """Generate execution plan from identities; optionally use analysis for duplicate handling.""" config = ctx.config logger = ctx.logger click.echo(f"Generating execution plan from: {input}") if analysis: click.echo(f"Using analysis: {analysis}") click.echo() identities_data = load_identities_json(input) movies_data = identities_data.get("movies", []) series_data = identities_data.get("series", []) anime_data = identities_data.get("anime", []) other_data = identities_data.get("other", []) click.echo( f"Loaded {len(movies_data)} movies, {len(series_data)} series, " f"{len(anime_data)} anime, {len(other_data)} other" ) click.echo() identities_list = identities_to_plan_input(identities_data) analysis_data = None if analysis: if not analysis.exists(): click.echo(f"Error: Analysis file not found: {analysis}", err=True) click.echo("Run 'vlm analyze' first to generate analysis.json.", err=True) raise FileNotFoundError(analysis) try: analysis_data = load_analysis_json(analysis) except (json.JSONDecodeError, KeyError) as e: click.echo(f"Error: Invalid or incomplete analysis file: {e}", err=True) click.echo("Run 'vlm analyze' to regenerate analysis.json.", err=True) raise click.echo("Generating execution plan...") # Load state to filter ignored files from vlm.state import StateManager state_path = Path.home() / ".vlm" / "state.json" state_manager = StateManager(state_path) ignored_paths = {str(s.file_path) for s in state_manager.query_by_status("ignored")} if ignored_paths: click.echo(f" Filtering {len(ignored_paths)} files marked as 'ignored' in state.") execution_plan = generate_plan( identities_list, config, analysis_data=analysis_data, ignored_paths=ignored_paths ) click.echo() click.echo("Plan generation complete!") click.echo() click.echo("Operation summary:") click.echo(f" Total operations: {execution_plan.summary['total']}") click.echo(f" Move operations: {execution_plan.summary['move']}") click.echo(f" Rename operations: {execution_plan.summary['rename']}") click.echo(f" Quarantine operations: {execution_plan.summary['quarantine']}") click.echo(f" No-op (skipped): {execution_plan.summary['no-op']}") conflicts = sum(1 for op in execution_plan.operations if op.has_conflict) if conflicts > 0: click.echo() click.echo(f" ⚠️ Conflicts detected: {conflicts}") click.echo(" Review the plan file for details on conflicting operations.") # Check for directory warnings directory_warning = execution_plan.metadata.get("directory_warning", False) if directory_warning: emptied_dirs = execution_plan.metadata.get("emptied_directories", []) click.echo() click.echo(f" ⚠️ Directory preservation warning: {len(emptied_dirs)} directories will be emptied") click.echo(" These directories will be preserved but may be empty after execution.") click.echo(" Review the plan file for details on preserved directories.") click.echo() click.echo(f"Saving execution plan to: {output}") output.parent.mkdir(parents=True, exist_ok=True) save_plan(execution_plan, output) click.echo("Execution plan saved successfully!") click.echo() click.echo("Next steps:") click.echo(f" 1. Review the plan: {output}") click.echo(" 2. Edit the plan if needed (it's JSON)") click.echo(f" 3. Dry-run: vlm execute --plan {output}") click.echo(f" 4. Execute: vlm execute --plan {output} --confirm") logger.info( f"Plan generated: {execution_plan.summary['total']} operations, " f"{conflicts} conflicts, saved to {output}" )