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;
}
}
}
}