# plotting.py

import logging
import time
from threading import Thread

from flask import jsonify, request

from .. import progress, ut
from ..axicontroller import AxiController


def register(app, ctx) -> None:
    @app.route('/plot_layers', methods=['POST'])
    def plot_layers():
        payload = request.get_json(silent=True) or {}
        project = payload.get('project')
        layers = payload.get('layers')
        if not project or not isinstance(layers, list) or not layers:
            return jsonify({'error': 'project and layers are required'}), 400

        if ctx.plot_in_progress or (ctx.plot_thread is not None and ctx.plot_thread.is_alive()):
            return jsonify({'error': 'plot already in progress'}), 409
        if ctx.get_paused_job() is not None:
            return jsonify({'error': 'plot paused; resume required'}), 409

        plotted = []
        targets = []
        for layer in layers:
            if not isinstance(layer, str) or not layer:
                return jsonify({'error': 'layers must be non-empty strings'}), 400
            rel_path = f'{project}/{layer}'
            target = ut.resolve_file_path(ctx.file_root, rel_path)
            if target is None:
                return jsonify({'error': f'Invalid path: {rel_path}'}), 400
            if not target.exists() or not target.is_file():
                return jsonify({'error': f'File not found: {rel_path}'}), 404
            if target.suffix.lower() != '.svg':
                return jsonify({'error': f'Not an SVG: {rel_path}'}), 400
            targets.append((rel_path, target))

        def _plot_worker():
            plot_ad = AxiController()
            allow_estimate = True
            ctx.plot_in_progress = True
            ctx.update_plot_state(
                status='running',
                message='',
                error='',
                current_layer=0,
                total_layers=len(targets),
                progress_mm=0.0,
                progress_raw_mm=None,
                total_mm=0.0,
                started_at=time.time(),
                finished_at=None,
                resume_available=False,
                paused_layer=None,
                paused_layer_path=None,
            )
            try:
                for idx, (rel_path, target) in enumerate(targets, start=1):
                    ctx.update_plot_state(
                        current_layer=idx,
                        progress_mm=0.0,
                        progress_raw_mm=None,
                        total_mm=0.0,
                        message=f'Plotting {rel_path}',
                    )
                    logging.info('PLOT_FILE %s', rel_path)

                    plot_ad.plot_status.progress.total = 0
                    output_svg, status_code = progress.run_with_progress(
                        plot_ad,
                        lambda: plot_ad.plot_file(
                            str(target),
                            enable_progress=True,
                            allow_estimate=allow_estimate,
                            return_output=True,
                        ),
                        is_resume=False,
                        update_plot_state=ctx.update_plot_state,
                        get_existing_total=ctx.get_existing_total,
                    )
                    if status_code == 102 and output_svg:
                        with ctx.plot_state_lock:
                            paused_total_mm = float(ctx.plot_state.get('total_mm') or 0.0)
                            paused_progress_mm = float(ctx.plot_state.get('progress_mm') or 0.0)
                        ctx.set_paused_job(
                            {
                                'targets': targets,
                                'paused_index': idx,
                                'paused_layer_path': rel_path,
                                'paused_svg': output_svg,
                                'paused_total_mm': paused_total_mm,
                                'paused_progress_mm': paused_progress_mm,
                            }
                        )
                        ctx.update_plot_state(
                            status='paused',
                            message='Plot paused by button press',
                            finished_at=time.time(),
                            resume_available=True,
                            paused_layer=idx,
                            paused_layer_path=rel_path,
                        )
                        return
                    if status_code not in (0,):
                        raise RuntimeError(f'Plot stopped with code {status_code}')
                    plotted.append(rel_path)

                with ctx.plot_state_lock:
                    final_progress = float(ctx.plot_state.get('progress_mm') or 0.0)
                    final_total = float(ctx.plot_state.get('total_mm') or 0.0)
                if final_progress > 0.0:
                    final_total = final_progress
                ctx.update_plot_state(
                    status='done',
                    message='Plot complete',
                    finished_at=time.time(),
                    total_mm=final_total,
                    progress_raw_mm=None,
                )
            except Exception as exc:
                logging.exception('Plot failed')
                ctx.update_plot_state(
                    status='error',
                    error=str(exc),
                    finished_at=time.time(),
                    progress_raw_mm=None,
                )
            finally:
                ctx.plot_in_progress = False

        ctx.plot_thread = Thread(target=_plot_worker, daemon=True)
        ctx.plot_thread.start()
        return jsonify({'status': 'started', 'plotted': plotted})

    @app.route('/resume_plot', methods=['POST'])
    def resume_plot():
        if ctx.plot_in_progress or (ctx.plot_thread is not None and ctx.plot_thread.is_alive()):
            return jsonify({'error': 'plot already in progress'}), 409

        job = ctx.get_paused_job()
        if job is None:
            return jsonify({'error': 'no paused plot to resume'}), 409

        ctx.set_paused_job(None)

        def _resume_worker():
            plot_ad = AxiController()
            ctx.plot_in_progress = True
            targets = job['targets']
            paused_index = job['paused_index']
            paused_layer_path = job['paused_layer_path']
            paused_total_mm = float(job.get('paused_total_mm') or 0.0)
            paused_progress_mm = float(job.get('paused_progress_mm') or 0.0)
            ctx.update_plot_state(
                status='running',
                message=f'Resuming {paused_layer_path}',
                error='',
                current_layer=paused_index,
                total_layers=len(targets),
                progress_mm=paused_progress_mm,
                progress_raw_mm=None,
                total_mm=paused_total_mm,
                started_at=time.time(),
                finished_at=None,
                resume_available=False,
                paused_layer=None,
                paused_layer_path=None,
            )
            try:
                plot_ad.plot_status.progress.total = 0
                output_svg, status_code = progress.run_with_progress(
                    plot_ad,
                    lambda: plot_ad.resume_plot(job['paused_svg'], enable_progress=True),
                    is_resume=True,
                    update_plot_state=ctx.update_plot_state,
                    get_existing_total=ctx.get_existing_total,
                )
                if status_code == 102 and output_svg:
                    with ctx.plot_state_lock:
                        paused_total_mm = float(ctx.plot_state.get('total_mm') or 0.0)
                        paused_progress_mm = float(ctx.plot_state.get('progress_mm') or 0.0)
                    ctx.set_paused_job(
                        {
                            'targets': targets,
                            'paused_index': paused_index,
                            'paused_layer_path': paused_layer_path,
                            'paused_svg': output_svg,
                            'paused_total_mm': paused_total_mm,
                            'paused_progress_mm': paused_progress_mm,
                        }
                    )
                    ctx.update_plot_state(
                        status='paused',
                        message='Plot paused by button press',
                        finished_at=time.time(),
                        resume_available=True,
                        paused_layer=paused_index,
                        paused_layer_path=paused_layer_path,
                    )
                    return
                if status_code not in (0,):
                    raise RuntimeError(f'Plot stopped with code {status_code}')

                plotted = [paused_layer_path]
                for idx, (rel_path, target) in enumerate(targets[paused_index:], start=paused_index + 1):
                    ctx.update_plot_state(
                        current_layer=idx,
                        progress_mm=0.0,
                        progress_raw_mm=None,
                        total_mm=0.0,
                        message=f'Plotting {rel_path}',
                    )
                    logging.info('PLOT_FILE %s', rel_path)

                    plot_ad.plot_status.progress.total = 0
                    output_svg, status_code = progress.run_with_progress(
                        plot_ad,
                        lambda: plot_ad.plot_file(
                            str(target),
                            enable_progress=True,
                            allow_estimate=False,
                            return_output=True,
                        ),
                        is_resume=False,
                        update_plot_state=ctx.update_plot_state,
                        get_existing_total=ctx.get_existing_total,
                    )
                    if status_code == 102 and output_svg:
                        ctx.set_paused_job(
                            {
                                'targets': targets,
                                'paused_index': idx,
                                'paused_layer_path': rel_path,
                                'paused_svg': output_svg,
                            }
                        )
                        ctx.update_plot_state(
                            status='paused',
                            message='Plot paused by button press',
                            finished_at=time.time(),
                            resume_available=True,
                            paused_layer=idx,
                            paused_layer_path=rel_path,
                        )
                        return
                    if status_code not in (0,):
                        raise RuntimeError(f'Plot stopped with code {status_code}')
                    plotted.append(rel_path)

                with ctx.plot_state_lock:
                    final_progress = float(ctx.plot_state.get('progress_mm') or 0.0)
                    final_total = float(ctx.plot_state.get('total_mm') or 0.0)
                if final_progress > 0.0:
                    final_total = final_progress
                ctx.update_plot_state(
                    status='done',
                    message='Plot complete',
                    finished_at=time.time(),
                    total_mm=final_total,
                    progress_raw_mm=None,
                )
            except Exception as exc:
                logging.exception('Resume failed')
                ctx.update_plot_state(
                    status='error',
                    error=str(exc),
                    finished_at=time.time(),
                    progress_raw_mm=None,
                )
            finally:
                ctx.plot_in_progress = False

        ctx.plot_thread = Thread(target=_resume_worker, daemon=True)
        ctx.plot_thread.start()
        return jsonify({'status': 'resuming'})

    @app.route('/discard_pause', methods=['POST'])
    def discard_pause():
        if ctx.plot_in_progress or (ctx.plot_thread is not None and ctx.plot_thread.is_alive()):
            return jsonify({'error': 'plot already in progress'}), 409
        if ctx.get_paused_job() is None:
            return jsonify({'error': 'no paused plot to discard'}), 409

        ctx.set_paused_job(None)
        ctx.update_plot_state(
            status='idle',
            message='Paused plot discarded',
            error='',
            progress_raw_mm=None,
            resume_available=False,
            paused_layer=None,
            paused_layer_path=None,
        )
        return jsonify({'status': 'discarded'})
