using aeqw89.DataKeys; using Beam.Dynamic; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; namespace Beam.Temporary.Cli { /// /// Type‑safe, staged builder that prevents callers from forgetting the mandatory steps /// (source → link selection → transformer) and surfaces operational knobs as first‑class /// methods instead of magic parameters. /// public static class DownloadBuilder { /* ──────────────────────────── Entry points ─────────────────────────── */ public static ILinkStage FromMeta(DataKey novelKey, BeamDataDictionary data) => Create(novelKey, data, SourceKind.Meta); public static ILinkStage FromText(DataKey novelKey, BeamDataDictionary data) => Create(novelKey, data, SourceKind.Text); /* ────────────────────────────── Stages ─────────────────────────────── */ public interface ILinkStage { ITransformStage WithLink(); ITransformStage WithLinkGenerator(); } public interface ITransformStage { IContextStage WithTransformer(Func> factory); } public interface IContextStage { IContextStage Configure(Action> configure); IContextStage WithParallelism(int degree); IContextStage WithTimeout(TimeSpan timeout); IContextStage WithRetryReporter(IProgress reporter); DownloadEnumerable Build(); } /* ────────────────────────── Implementation ────────────────────────── */ private enum SourceKind { Meta, Text } private static ILinkStage Create(DataKey novelKey, BeamDataDictionary data, SourceKind kind) { var (source, initial) = Resolve(novelKey, data, kind); var ctxBuilder = new DownloadContextBuilder().WithLinks(Array.Empty()); // placeholder, filled later. return new LinkStage(source, initial, data, ctxBuilder); } private static (WebResource Source, State Initial) Resolve(DataKey novelKey, BeamDataDictionary data, SourceKind kind) { if (!data.Novels.TryGetValue(novelKey, out var tr)) throw new KeyNotFoundException($"Novel '{novelKey}' not found in BeamDataDictionary."); var textRecord = tr.ToRecord(data); WebResource? source; State? initial; if (kind == SourceKind.Meta) { source = textRecord.AssociatedMetaSource ?? throw new InvalidOperationException($"Meta source missing for '{novelKey}'."); initial = textRecord.Resource.MetaTemplateInitialData ?? throw new InvalidOperationException("Meta template data missing."); } else { source = textRecord.AssociatedSource ?? throw new InvalidOperationException($"Text source missing for '{novelKey}'."); initial = textRecord.Resource.TemplateInitialData ?? throw new InvalidOperationException("Template initial data missing."); } return (source, initial); } /* ──────────────────────────── Stage types ─────────────────────────── */ private sealed record LinkStage( WebResource Source, State Initial, BeamDataDictionary Data, DownloadContextBuilder CtxBuilder) : ILinkStage { public ITransformStage WithLink() { var link = Data.Templates[Source.Key].Builder.Build(Initial); CtxBuilder.WithLinks(new[] { link }); return new TransformStage(Source, Data, CtxBuilder); } public ITransformStage WithLinkGenerator() { var template = Data.Templates[Source.Key]; var generator = SourceLinkEnumerable.FromGenerator(new OrderedSourceLinkGenerator( template.Builder, new NumberedStateChanger(template.Factory.Behavior), Initial)); CtxBuilder.WithLinks(generator); return new TransformStage(Source, Data, CtxBuilder); } } private sealed record TransformStage( WebResource Source, BeamDataDictionary Data, DownloadContextBuilder CtxBuilder) : ITransformStage { public IContextStage WithTransformer(Func> factory) { var transformer = factory(Data.Bindings[Source.Bindings]); CtxBuilder.WithTransformer(transformer); return new ContextStage(CtxBuilder); } } private sealed class ContextStage : IContextStage { private readonly DownloadContextBuilder _ctxBuilder; private int _parallelism = 4; public ContextStage(DownloadContextBuilder ctxBuilder) => _ctxBuilder = ctxBuilder; public IContextStage Configure(Action> configure) { configure(_ctxBuilder); return this; } public IContextStage WithParallelism(int degree) { _parallelism = Math.Max(1, degree); return this; } public IContextStage WithTimeout(TimeSpan timeout) { _ctxBuilder.WithTimeOut(timeout); return this; } public IContextStage WithRetryReporter(IProgress reporter) { _ctxBuilder.WithRetryReporter(reporter); return this; } public DownloadEnumerable Build() { var context = _ctxBuilder.Build(); SequentialFragmentDownloader sequentialDownloader = new( context, ctx => new UnitFragmentDownloader( context.Web, context.AsyncTranformer, context.AsyncFailurePredicates, _parallelism, context.DownloadLogger), context.DownloadLogger); var enumerable = new DownloadEnumerable( sequentialDownloader .UnwrapFragmented()); sequentialDownloader.DisposeAsync().AsTask().Wait(); return enumerable; } } } }